@astack-tech/integrations 0.2.0-beta.10 → 0.2.0-beta.12

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @astack-tech/integrations
2
2
 
3
+ ## 0.2.0-beta.12
4
+
5
+ ### Patch Changes
6
+
7
+ - a395d53: The READMEs lead with the model you choose: `OpenAICompatible` for OpenAI, Qwen, local models through Ollama and any other OpenAI-compatible API, `Deepseek` as a preset, and any object with `chatCompletion()` for everything else.
8
+
9
+ ## 0.2.0-beta.11
10
+
11
+ ### Patch Changes
12
+
13
+ - 8098657: The `prompt` and `messages` ports of `Deepseek` and `OpenAICompatible` check their input. A value of the wrong type rejects the pipeline run with a `TypeError` before the API is called.
14
+ - Updated dependencies [fa0689e]
15
+ - @astack-tech/core@0.2.0-beta.5
16
+
3
17
  ## 0.2.0-beta.10
4
18
 
5
19
  ### Patch Changes
package/README.md CHANGED
@@ -19,28 +19,45 @@ npm install @astack-tech/integrations @astack-tech/core
19
19
  ```typescript
20
20
  import { ModelProvider } from '@astack-tech/integrations';
21
21
 
22
- const model = new ModelProvider.Deepseek({ apiKey: process.env['DEEPSEEK_API_KEY'] ?? '' });
22
+ // OpenAI, or any API that speaks the OpenAI chat completions protocol
23
+ const model = new ModelProvider.OpenAICompatible({
24
+ apiKey: process.env['OPENAI_API_KEY'] ?? '',
25
+ model: 'gpt-4o-mini',
26
+ });
23
27
 
24
28
  const text = await model.run('What is flow-based programming?');
25
29
  const reply = await model.chatCompletion([{ role: 'user', content: 'Hello' }]);
26
30
  ```
27
31
 
28
- `Deepseek` and `OpenAICompatible` are also available from the subpath: `import { Deepseek, OpenAICompatible } from '@astack-tech/integrations/model-provider'`.
32
+ `OpenAICompatible` and `Deepseek` are also available from the subpath: `import { OpenAICompatible, Deepseek } from '@astack-tech/integrations/model-provider'`.
29
33
 
30
- ## Deepseek
34
+ ## Choose a model
35
+
36
+ | Model | Provider |
37
+ | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
38
+ | OpenAI | `new ModelProvider.OpenAICompatible({ apiKey, model: 'gpt-4o-mini' })` |
39
+ | DeepSeek | `new ModelProvider.Deepseek({ apiKey })` |
40
+ | Qwen, Moonshot and other OpenAI-compatible APIs | `new ModelProvider.OpenAICompatible({ apiKey, baseURL, model })` |
41
+ | Local models through Ollama or vLLM | `new ModelProvider.OpenAICompatible({ apiKey: 'ollama', baseURL: 'http://localhost:11434/v1', model: 'llama3.1' })` |
42
+ | Any other API | Implement the `ModelProvider` interface of `@astack-tech/components` (`chatCompletion(messages)`) |
43
+
44
+ Every provider satisfies the `ModelProvider` interface of `@astack-tech/components`, so it can drive `Agent` and `StreamingAgent` directly.
45
+
46
+ ## OpenAICompatible
31
47
 
32
- Uses the OpenAI-compatible DeepSeek API through the official `openai` client.
48
+ Calls any API that speaks the OpenAI chat completions protocol through the official `openai` client: OpenAI and the compatible endpoints of DeepSeek, Qwen, Moonshot, Ollama, vLLM and others. Tool calling and streaming depend on what the endpoint supports.
33
49
 
34
- | Option | Default | Description |
35
- | -------------------- | ----------------------------- | -------------------------------------------------------------------- |
36
- | `apiKey` | — | DeepSeek API key |
37
- | `model` | `deepseek-chat` | Model name |
38
- | `baseURL` | `https://api.deepseek.com/v1` | API base URL |
39
- | `temperature` | `0.7` | Sampling temperature |
40
- | `maxTokens`, `topP` | not sent | Optional sampling limits |
41
- | `systemPrompt` | none | Added before every request |
42
- | `rawTools` / `tools` | none | Tools as `{ name, description, parameters }` or in the OpenAI format |
43
- | `verbose` | `false` | Log API errors to the console |
50
+ | Option | Default | Description |
51
+ | -------------------- | --------------------------- | -------------------------------------------------------------------- |
52
+ | `apiKey` | — | API key of the provider |
53
+ | `model` | — | Model name (required) |
54
+ | `baseURL` | `https://api.openai.com/v1` | API base URL |
55
+ | `name` | `The model API` | Provider name in errors and logs |
56
+ | `temperature` | `0.7` | Sampling temperature |
57
+ | `maxTokens`, `topP` | not sent | Optional sampling limits |
58
+ | `systemPrompt` | none | Added before every request |
59
+ | `rawTools` / `tools` | none | Tools as `{ name, description, parameters }` or in the OpenAI format |
60
+ | `verbose` | `false` | Log API errors to the console |
44
61
 
45
62
  | Method | Returns |
46
63
  | --------------------------------------------------------------- | -------------------------------------------------------------- |
@@ -56,33 +73,27 @@ Messages are checked before the request: the role must be `system`, `user`, `ass
56
73
  | `prompt` | `completion` |
57
74
  | `messages` | `message` |
58
75
 
59
- `Deepseek` satisfies the `ModelProvider` interface of `@astack-tech/components`, so it can drive `Agent` and `StreamingAgent` directly.
76
+ A value of the wrong type on an input port rejects the pipeline run with a `TypeError` before the API is called.
60
77
 
61
- ## OpenAICompatible
78
+ ## Deepseek
62
79
 
63
- Calls any API that speaks the OpenAI chat completions protocol: OpenAI and the compatible endpoints of DeepSeek, Qwen, Moonshot, Ollama, vLLM and others. `Deepseek` is this provider with DeepSeek's defaults, so methods, ports and the other options are the same.
80
+ `OpenAICompatible` with DeepSeek's defaults. Methods, ports and the other options are the same.
64
81
 
65
82
  ```typescript
66
- import { ModelProvider } from '@astack-tech/integrations';
67
-
68
- const model = new ModelProvider.OpenAICompatible({
69
- apiKey: process.env['OPENAI_API_KEY'] ?? '',
70
- model: 'gpt-4o-mini',
71
- });
83
+ const model = new ModelProvider.Deepseek({ apiKey: process.env['DEEPSEEK_API_KEY'] ?? '' });
72
84
  ```
73
85
 
74
- | Option | Default | Description |
75
- | --------- | --------------------------- | -------------------------------- |
76
- | `apiKey` | — | API key of the provider |
77
- | `model` | — | Model name (required) |
78
- | `baseURL` | `https://api.openai.com/v1` | API base URL |
79
- | `name` | `The model API` | Provider name in errors and logs |
86
+ | Option | Default | Description |
87
+ | --------- | ----------------------------- | ---------------- |
88
+ | `apiKey` | — | DeepSeek API key |
89
+ | `model` | `deepseek-chat` | Model name |
90
+ | `baseURL` | `https://api.deepseek.com/v1` | API base URL |
80
91
 
81
92
  ## Learn more
82
93
 
83
94
  - [API reference](https://astack.tech/docs/api/integrations): every export, generated from the source
84
95
  - [Model providers](https://astack.tech/docs/model-providers), including how to write your own
85
- - Example: [chat](https://astack.tech/examples/chat)
96
+ - Examples: [chat](https://astack.tech/examples/chat), [custom model](https://astack.tech/examples/custom-model)
86
97
 
87
98
  ## The AStack packages
88
99
 
package/dist/index.cjs CHANGED
@@ -51,6 +51,21 @@ var FALLBACK_TEXT = "API call failed";
51
51
  function isRecord(value) {
52
52
  return typeof value === "object" && value !== null && !Array.isArray(value);
53
53
  }
54
+ function isOptionalString(value) {
55
+ return value === void 0 || typeof value === "string";
56
+ }
57
+ function isCount(value) {
58
+ return typeof value === "number" && Number.isFinite(value);
59
+ }
60
+ function isTokenUsage(value) {
61
+ return isRecord(value) && isCount(value["completion_tokens"]) && isCount(value["prompt_tokens"]) && isCount(value["total_tokens"]) && (value["prompt_cache_hit_tokens"] === void 0 || isCount(value["prompt_cache_hit_tokens"])) && (value["prompt_cache_miss_tokens"] === void 0 || isCount(value["prompt_cache_miss_tokens"]));
62
+ }
63
+ function isMessage(value) {
64
+ return isRecord(value) && typeof value["role"] === "string" && isOptionalString(value["content"]) && (value["tool_calls"] === void 0 || Array.isArray(value["tool_calls"])) && isOptionalString(value["tool_call_id"]) && (value["usage"] === void 0 || isTokenUsage(value["usage"]));
65
+ }
66
+ function isMessageList(value) {
67
+ return Array.isArray(value) && value.every((item) => isMessage(item));
68
+ }
54
69
  function toApiTool(tool) {
55
70
  const raw = isRecord(tool) ? tool : {};
56
71
  const definition = raw["function"];
@@ -225,11 +240,15 @@ var OpenAICompatible = class extends import_core.Component {
225
240
  }
226
241
  /**
227
242
  * Connects the `prompt` and `messages` input ports to the model.
228
- * API errors produce a fallback value so the pipeline keeps flowing.
243
+ * API errors produce a fallback value so the pipeline keeps flowing. Input of the wrong
244
+ * type fails the pipeline run with a `TypeError` before the API is called.
229
245
  */
230
246
  _transform($i, $o) {
231
247
  $i("prompt").receive((prompt) => {
232
- this.generateCompletion(prompt).then(
248
+ if (typeof prompt !== "string") {
249
+ throw new TypeError(`${this.name}: the prompt port expects a string.`);
250
+ }
251
+ return this.generateCompletion(prompt).then(
233
252
  (completion) => {
234
253
  $o("completion").send(completion);
235
254
  },
@@ -240,7 +259,10 @@ var OpenAICompatible = class extends import_core.Component {
240
259
  );
241
260
  });
242
261
  $i("messages").receive((messages) => {
243
- this.chatCompletion(messages).then(
262
+ if (!isMessageList(messages)) {
263
+ throw new TypeError(`${this.name}: the messages port expects an array of messages.`);
264
+ }
265
+ return this.chatCompletion(messages).then(
244
266
  (message) => {
245
267
  $o("message").send(message);
246
268
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/model-provider/index.ts","../src/model-provider/openai-compatible/index.ts","../src/model-provider/deepseek/index.ts"],"sourcesContent":["/**\n * Model provider integrations for AStack. `ModelProvider.OpenAICompatible` calls any API\n * that speaks the OpenAI chat completions protocol, and `ModelProvider.Deepseek` is that\n * provider set up for DeepSeek. Both satisfy the `ModelProvider` interface of\n * `@astack-tech/components`.\n *\n * @packageDocumentation\n */\n\nexport * as ModelProvider from './model-provider';\n","export {\n default as Deepseek,\n type AssistantMessage as DeepseekAssistantMessage,\n type ChatCompletionOptions as DeepseekChatCompletionOptions,\n type DeepseekConfig,\n type Message as DeepseekMessage,\n type MessageRole as DeepseekMessageRole,\n type RawTool as DeepseekRawTool,\n type ResponseToolCall as DeepseekResponseToolCall,\n type StreamChunk as DeepseekStreamChunk,\n type StreamToolCall as DeepseekStreamToolCall,\n type TokenUsage as DeepseekTokenUsage,\n type ToolCall as DeepseekToolCall,\n} from './deepseek';\nexport {\n default as OpenAICompatible,\n type AssistantMessage,\n type ChatCompletionOptions,\n type Message,\n type MessageRole,\n type OpenAICompatibleConfig,\n type RawTool,\n type ResponseToolCall,\n type StreamChunk,\n type StreamToolCall,\n type TokenUsage,\n type ToolCall,\n} from './openai-compatible';\n","import { Component } from '@astack-tech/core';\nimport type { Port } from '@astack-tech/core';\nimport OpenAI from 'openai';\n\n/**\n * Options for a provider that speaks the OpenAI chat completions API.\n */\nexport interface OpenAICompatibleConfig {\n /** API key of the provider. */\n apiKey: string;\n /** Model name, for example `gpt-4o-mini`, `qwen-plus` or `llama3.1`. */\n model: string;\n /** API base URL. Defaults to `https://api.openai.com/v1`. */\n baseURL?: string | undefined;\n /** Provider name used in errors and logs. Defaults to `The model API`. */\n name?: string | undefined;\n /** Sampling temperature. Defaults to `0.7`. */\n temperature?: number | undefined;\n /** Maximum number of generated tokens. */\n maxTokens?: number | undefined;\n /** Nucleus sampling probability. */\n topP?: number | undefined;\n /** System prompt added before every request. */\n systemPrompt?: string | undefined;\n /**\n * Tools given as `{ name, description, parameters }` objects.\n * When this list is not empty, it takes precedence over `tools`.\n */\n rawTools?: RawTool[] | undefined;\n /** Tools in the OpenAI-compatible `{ type: 'function', function }` format. */\n tools?: unknown[] | undefined;\n /** Allows the OpenAI client to run in a browser. Defaults to `false`. */\n dangerouslyAllowBrowser?: boolean | undefined;\n /** Logs API errors to the console. Defaults to `false`. */\n verbose?: boolean | undefined;\n}\n\n/**\n * Tool description accepted by `rawTools` and `temporaryTools`.\n */\nexport interface RawTool {\n /** Unique tool name. */\n name: string;\n /** Description that tells the model when to use the tool. */\n description: string;\n /** JSON Schema of the arguments. Defaults to `{}`. */\n parameters?: Record<string, unknown> | undefined;\n}\n\n/**\n * Role of a chat message.\n */\nexport type MessageRole = 'system' | 'user' | 'assistant' | 'tool';\n\n/**\n * Tool call in the internal format.\n */\nexport interface ToolCall {\n /** Identifier assigned by the API. */\n id?: string | undefined;\n /** Name of the tool. */\n tool_name: string;\n /** Parsed arguments. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Tool call returned by `chatCompletion`. It carries both the OpenAI shape\n * (`type`, `function`) and the internal shape (`tool_name`, `arguments`).\n */\nexport interface ResponseToolCall {\n /** Identifier assigned by the API. A `tool` message answers the call with it. */\n id: string;\n /** Always `function`. */\n type: 'function';\n /** The call in the OpenAI shape: function name and raw JSON arguments. An empty argument string is sent as `{}`. */\n function: {\n /** Name of the tool to call. */\n name: string;\n /** Arguments as JSON text. */\n arguments: string;\n };\n /** Name of the tool, same as `function.name`. */\n tool_name: string;\n /** Parsed arguments. JSON that is not an object becomes `{}`. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Partial tool call emitted while streaming. `arguments` holds the raw JSON fragment.\n */\nexport interface StreamToolCall {\n /** Identifier assigned by the API. Only the first delta of a call carries it; later deltas have `''`. */\n id: string;\n /** Always `function`. */\n type: 'function';\n /** The delta in the OpenAI shape. `arguments` holds only this delta's JSON fragment. */\n function: {\n /** Name of the tool to call. */\n name: string;\n /** Arguments as JSON text. */\n arguments: string;\n };\n /** Name of the tool when this delta carries it, otherwise `''`. */\n tool_name: string;\n /** Raw JSON fragment of this delta. Concatenate the fragments of a call to get its arguments. */\n arguments: string;\n}\n\n/**\n * Token usage reported by the API.\n */\nexport interface TokenUsage {\n /** Tokens in the generated reply. */\n completion_tokens: number;\n /** Tokens in the request. */\n prompt_tokens: number;\n /** Prompt tokens served from the provider's context cache (reported by DeepSeek). */\n prompt_cache_hit_tokens?: number | undefined;\n /** Prompt tokens not served from the cache. */\n prompt_cache_miss_tokens?: number | undefined;\n /** Sum of prompt and completion tokens. */\n total_tokens: number;\n}\n\n/**\n * Chat message accepted as input. It matches the agent message contract in\n * `@astack-tech/components`, so agent histories can be passed directly.\n */\nexport interface Message {\n /** One of `system`, `user`, `assistant` or `tool` (see `MessageRole`). */\n role: string;\n /** Text content. Omitted content is sent as no content. */\n content?: string | undefined;\n /** Accepted for compatibility. Assistant tool calls are not forwarded to the API. */\n tool_calls?: readonly unknown[] | undefined;\n /** Identifier of the call that a `tool` message answers. */\n tool_call_id?: string | undefined;\n /** Ignored on input. Present so agent histories can be passed unchanged. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Assistant message returned by `chatCompletion`.\n */\nexport interface AssistantMessage {\n /** Always `assistant`. */\n role: 'assistant';\n /** Reply text. Empty when the model only calls tools. */\n content: string;\n /** Tool calls requested by the model, if any. */\n tool_calls?: ResponseToolCall[] | undefined;\n /** Token usage of this request, when the API reports it. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Chunk yielded by `streamChatCompletion`.\n */\nexport interface StreamChunk {\n /** Always `assistant`. */\n role: 'assistant';\n /** Text delta. Empty in chunks that carry tool calls or usage. */\n content: string;\n /** Tool-call deltas in this chunk. */\n tool_calls?: StreamToolCall[] | undefined;\n /** Token usage. Sent once, in the final chunk, when the API reports it. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Per-call options.\n */\nexport interface ChatCompletionOptions {\n /** Tools for this call only. They replace the configured tools. */\n temporaryTools?: unknown[] | undefined;\n}\n\ntype ChatMessageParam = OpenAI.Chat.ChatCompletionMessageParam;\ntype ChatTool = OpenAI.Chat.ChatCompletionTool;\n\ninterface ApiUsage {\n completion_tokens?: number;\n prompt_tokens?: number;\n total_tokens?: number;\n prompt_cache_hit_tokens?: number;\n prompt_cache_miss_tokens?: number;\n}\n\nconst DEFAULT_BASE_URL = 'https://api.openai.com/v1';\nconst DEFAULT_NAME = 'The model API';\nconst DEFAULT_TEMPERATURE = 0.7;\nconst FALLBACK_TEXT = 'API call failed';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Converts a `{ name, description, parameters }` object to the OpenAI tool format.\n * Objects already in the OpenAI format are returned unchanged.\n */\nfunction toApiTool(tool: unknown): ChatTool {\n const raw = isRecord(tool) ? tool : {};\n const definition = raw['function'];\n if (raw['type'] === 'function' && isRecord(definition)) {\n const { name, description, parameters, strict } = definition;\n return {\n type: 'function',\n function: {\n name: typeof name === 'string' ? name : '',\n ...(typeof description === 'string' ? { description } : {}),\n ...(isRecord(parameters) ? { parameters } : {}),\n ...(typeof strict === 'boolean' ? { strict } : {}),\n },\n };\n }\n const { name, description, parameters } = raw;\n return {\n type: 'function',\n function: {\n name: typeof name === 'string' ? name : '',\n description: typeof description === 'string' ? description : '',\n parameters: isRecord(parameters) ? parameters : {},\n },\n };\n}\n\n/**\n * Parses tool-call arguments. Anything that is not a JSON object becomes `{}`.\n */\nfunction parseArguments(raw: string): Record<string, unknown> {\n if (raw.trim().length === 0) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n return isRecord(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\nfunction mapUsage(usage: ApiUsage): TokenUsage {\n return {\n completion_tokens: usage.completion_tokens ?? 0,\n prompt_tokens: usage.prompt_tokens ?? 0,\n total_tokens: usage.total_tokens ?? 0,\n ...(usage.prompt_cache_hit_tokens === undefined\n ? {}\n : { prompt_cache_hit_tokens: usage.prompt_cache_hit_tokens }),\n ...(usage.prompt_cache_miss_tokens === undefined\n ? {}\n : { prompt_cache_miss_tokens: usage.prompt_cache_miss_tokens }),\n };\n}\n\n/**\n * Model provider for any API that speaks the OpenAI chat completions protocol:\n * OpenAI itself, and the compatible endpoints of DeepSeek, Qwen, Moonshot, Ollama,\n * vLLM and others. Set `baseURL` and `model` for the provider you use.\n *\n * Ports:\n * - `prompt` (in): a text prompt. The answer is sent on `completion`.\n * - `messages` (in): a list of chat messages. The reply is sent on `message`.\n */\nclass OpenAICompatible extends Component {\n private readonly client: OpenAI;\n private readonly model: string;\n private readonly temperature: number;\n private readonly maxTokens: number | undefined;\n private readonly topP: number | undefined;\n private readonly systemPrompt: string | undefined;\n private readonly tools: unknown[] | undefined;\n private readonly verbose: boolean;\n private readonly name: string;\n\n constructor(config: OpenAICompatibleConfig) {\n super({});\n\n this.client = new OpenAI({\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n dangerouslyAllowBrowser: config.dangerouslyAllowBrowser ?? false,\n });\n\n this.model = config.model;\n this.name = config.name ?? DEFAULT_NAME;\n this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;\n this.maxTokens = config.maxTokens;\n this.topP = config.topP;\n this.systemPrompt = config.systemPrompt;\n this.verbose = config.verbose ?? false;\n this.tools =\n config.rawTools !== undefined && config.rawTools.length > 0\n ? config.rawTools.map(toApiTool)\n : config.tools;\n\n Component.Port.I('prompt').attach(this);\n Component.Port.I('messages').attach(this);\n Component.Port.O('completion').attach(this);\n Component.Port.O('message').attach(this);\n }\n\n /**\n * Generates a completion for a single prompt.\n *\n * @returns The text answer, or the JSON-encoded assistant message when it contains tool calls.\n */\n async generateCompletion(prompt: string): Promise<string> {\n const response = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: [...this.systemMessages(), { role: 'user', content: prompt }],\n ...this.toolsParam(this.tools),\n });\n\n const message = this.firstMessage(response);\n if (message.tool_calls !== undefined && message.tool_calls.length > 0) {\n return JSON.stringify(message);\n }\n return message.content ?? '';\n }\n\n /**\n * Sends a conversation and returns the assistant reply.\n */\n async chatCompletion(\n messages: Message[],\n options?: ChatCompletionOptions\n ): Promise<AssistantMessage> {\n const response = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: this.formatMessages(messages),\n ...this.toolsParam(options?.temporaryTools ?? this.tools),\n });\n\n const message = this.firstMessage(response);\n const result: AssistantMessage = { role: 'assistant', content: message.content ?? '' };\n\n if (response.usage !== undefined) {\n result.usage = mapUsage(response.usage);\n }\n\n if (message.tool_calls !== undefined && message.tool_calls.length > 0) {\n result.tool_calls = message.tool_calls.map(toolCall => {\n if (toolCall.type !== 'function') {\n throw new Error(`Unsupported tool call type: ${toolCall.type}`);\n }\n const rawArguments = toolCall.function.arguments;\n return {\n id: toolCall.id,\n type: 'function',\n function: {\n name: toolCall.function.name,\n arguments: rawArguments.length > 0 ? rawArguments : '{}',\n },\n tool_name: toolCall.function.name,\n arguments: parseArguments(rawArguments),\n };\n });\n }\n\n return result;\n }\n\n /**\n * Streams the assistant reply. Each chunk holds a text delta, tool-call deltas, or usage.\n */\n async *streamChatCompletion(\n messages: Message[],\n options?: ChatCompletionOptions\n ): AsyncGenerator<StreamChunk> {\n const stream = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: this.formatMessages(messages),\n ...this.toolsParam(options?.temporaryTools ?? this.tools),\n stream: true,\n });\n\n for await (const chunk of stream) {\n const delta = chunk.choices[0]?.delta;\n\n const text = delta?.content ?? '';\n if (text !== '') {\n yield { role: 'assistant', content: text };\n }\n\n if (delta?.tool_calls) {\n yield {\n role: 'assistant',\n content: '',\n tool_calls: delta.tool_calls.map(toolCall => {\n const name = toolCall.function?.name ?? '';\n const args = toolCall.function?.arguments ?? '';\n return {\n id: toolCall.id ?? '',\n type: 'function',\n function: { name, arguments: args },\n tool_name: name,\n arguments: args,\n };\n }),\n };\n }\n\n if (chunk.usage) {\n yield { role: 'assistant', content: '', usage: mapUsage(chunk.usage) };\n }\n }\n }\n\n /**\n * Runs the model outside a pipeline.\n *\n * @param input - A prompt string or a list of messages.\n */\n override run(input: string | Message[]): Promise<string | AssistantMessage> {\n return typeof input === 'string' ? this.generateCompletion(input) : this.chatCompletion(input);\n }\n\n /**\n * Connects the `prompt` and `messages` input ports to the model.\n * API errors produce a fallback value so the pipeline keeps flowing.\n */\n override _transform($i: Port, $o: Port): void {\n $i('prompt').receive((prompt: string) => {\n this.generateCompletion(prompt).then(\n completion => {\n $o('completion').send(completion);\n },\n (error: unknown) => {\n this.logError(error);\n $o('completion').send(FALLBACK_TEXT);\n }\n );\n });\n\n $i('messages').receive((messages: Message[]) => {\n this.chatCompletion(messages).then(\n message => {\n $o('message').send(message);\n },\n (error: unknown) => {\n this.logError(error);\n $o('message').send({ role: 'assistant', content: FALLBACK_TEXT });\n }\n );\n });\n }\n\n private baseRequest(): {\n model: string;\n temperature: number;\n max_tokens?: number;\n top_p?: number;\n } {\n return {\n model: this.model,\n temperature: this.temperature,\n ...(this.maxTokens === undefined ? {} : { max_tokens: this.maxTokens }),\n ...(this.topP === undefined ? {} : { top_p: this.topP }),\n };\n }\n\n private toolsParam(tools: unknown[] | undefined): { tools?: ChatTool[] } {\n return tools !== undefined && tools.length > 0 ? { tools: tools.map(toApiTool) } : {};\n }\n\n private systemMessages(): ChatMessageParam[] {\n return this.systemPrompt === undefined || this.systemPrompt === ''\n ? []\n : [{ role: 'system', content: this.systemPrompt }];\n }\n\n /**\n * Converts input messages to API messages. Assistant `tool_calls` are not forwarded:\n * agents send tool results back as user messages, and the API rejects assistant tool\n * calls that are not followed by tool messages.\n */\n private formatMessages(messages: Message[]): ChatMessageParam[] {\n return [...this.systemMessages(), ...messages.map(message => this.toApiMessage(message))];\n }\n\n /**\n * Converts one input message to its API shape.\n *\n * @throws Error when the role is not one of `MessageRole`, or a `tool` message has no\n * `tool_call_id`. The API would reject both.\n */\n private toApiMessage(message: Message): ChatMessageParam {\n const content = message.content ?? '';\n switch (message.role) {\n case 'system':\n return { role: 'system', content };\n case 'user':\n return { role: 'user', content };\n case 'assistant':\n return message.content === undefined\n ? { role: 'assistant' }\n : { role: 'assistant', content: message.content };\n case 'tool':\n if (message.tool_call_id === undefined || message.tool_call_id === '') {\n throw new Error(\n `${this.name}: a tool message needs the tool_call_id of the call it answers.`\n );\n }\n return { role: 'tool', content, tool_call_id: message.tool_call_id };\n default:\n throw new Error(\n `${this.name}: unsupported message role '${message.role}'. Use system, user, assistant or tool.`\n );\n }\n }\n\n private firstMessage(response: OpenAI.Chat.ChatCompletion): OpenAI.Chat.ChatCompletionMessage {\n const choice = response.choices[0];\n if (choice === undefined) {\n throw new Error(`${this.name} returned no choices.`);\n }\n return choice.message;\n }\n\n private logError(error: unknown): void {\n if (this.verbose) {\n // eslint-disable-next-line no-console -- opt-in diagnostics controlled by `verbose`.\n console.error(`[${this.name}] API call failed:`, error);\n }\n }\n}\n\nexport default OpenAICompatible;\n","import OpenAICompatible from '../openai-compatible';\nimport type { OpenAICompatibleConfig } from '../openai-compatible';\n\nexport type {\n AssistantMessage,\n ChatCompletionOptions,\n Message,\n MessageRole,\n RawTool,\n ResponseToolCall,\n StreamChunk,\n StreamToolCall,\n TokenUsage,\n ToolCall,\n} from '../openai-compatible';\n\nconst DEFAULT_BASE_URL = 'https://api.deepseek.com/v1';\nconst DEFAULT_MODEL = 'deepseek-chat';\n\n/**\n * Options for the DeepSeek model provider. They are the options of\n * `OpenAICompatible`, with DeepSeek's endpoint and model as defaults.\n */\nexport interface DeepseekConfig extends Omit<OpenAICompatibleConfig, 'model' | 'name'> {\n /** Model name. Defaults to `deepseek-chat`. */\n model?: string | undefined;\n /** API base URL. Defaults to `https://api.deepseek.com/v1`. */\n baseURL?: string | undefined;\n}\n\n/**\n * DeepSeek model provider: `OpenAICompatible` set up for the DeepSeek API.\n *\n * Ports:\n * - `prompt` (in): a text prompt. The answer is sent on `completion`.\n * - `messages` (in): a list of chat messages. The reply is sent on `message`.\n */\nclass Deepseek extends OpenAICompatible {\n constructor(config: DeepseekConfig) {\n super({\n ...config,\n model: config.model ?? DEFAULT_MODEL,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n name: 'DeepSeek',\n });\n }\n}\n\nexport default Deepseek;\n"],"mappings":"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","names":["name","description","parameters","OpenAI","DEFAULT_BASE_URL"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/model-provider/index.ts","../src/model-provider/openai-compatible/index.ts","../src/model-provider/deepseek/index.ts"],"sourcesContent":["/**\n * Model provider integrations for AStack. `ModelProvider.OpenAICompatible` calls any API\n * that speaks the OpenAI chat completions protocol, and `ModelProvider.Deepseek` is that\n * provider set up for DeepSeek. Both satisfy the `ModelProvider` interface of\n * `@astack-tech/components`.\n *\n * @packageDocumentation\n */\n\nexport * as ModelProvider from './model-provider';\n","export {\n default as Deepseek,\n type AssistantMessage as DeepseekAssistantMessage,\n type ChatCompletionOptions as DeepseekChatCompletionOptions,\n type DeepseekConfig,\n type Message as DeepseekMessage,\n type MessageRole as DeepseekMessageRole,\n type RawTool as DeepseekRawTool,\n type ResponseToolCall as DeepseekResponseToolCall,\n type StreamChunk as DeepseekStreamChunk,\n type StreamToolCall as DeepseekStreamToolCall,\n type TokenUsage as DeepseekTokenUsage,\n type ToolCall as DeepseekToolCall,\n} from './deepseek';\nexport {\n default as OpenAICompatible,\n type AssistantMessage,\n type ChatCompletionOptions,\n type Message,\n type MessageRole,\n type OpenAICompatibleConfig,\n type RawTool,\n type ResponseToolCall,\n type StreamChunk,\n type StreamToolCall,\n type TokenUsage,\n type ToolCall,\n} from './openai-compatible';\n","import { Component } from '@astack-tech/core';\nimport type { Port } from '@astack-tech/core';\nimport OpenAI from 'openai';\n\n/**\n * Options for a provider that speaks the OpenAI chat completions API.\n */\nexport interface OpenAICompatibleConfig {\n /** API key of the provider. */\n apiKey: string;\n /** Model name, for example `gpt-4o-mini`, `qwen-plus` or `llama3.1`. */\n model: string;\n /** API base URL. Defaults to `https://api.openai.com/v1`. */\n baseURL?: string | undefined;\n /** Provider name used in errors and logs. Defaults to `The model API`. */\n name?: string | undefined;\n /** Sampling temperature. Defaults to `0.7`. */\n temperature?: number | undefined;\n /** Maximum number of generated tokens. */\n maxTokens?: number | undefined;\n /** Nucleus sampling probability. */\n topP?: number | undefined;\n /** System prompt added before every request. */\n systemPrompt?: string | undefined;\n /**\n * Tools given as `{ name, description, parameters }` objects.\n * When this list is not empty, it takes precedence over `tools`.\n */\n rawTools?: RawTool[] | undefined;\n /** Tools in the OpenAI-compatible `{ type: 'function', function }` format. */\n tools?: unknown[] | undefined;\n /** Allows the OpenAI client to run in a browser. Defaults to `false`. */\n dangerouslyAllowBrowser?: boolean | undefined;\n /** Logs API errors to the console. Defaults to `false`. */\n verbose?: boolean | undefined;\n}\n\n/**\n * Tool description accepted by `rawTools` and `temporaryTools`.\n */\nexport interface RawTool {\n /** Unique tool name. */\n name: string;\n /** Description that tells the model when to use the tool. */\n description: string;\n /** JSON Schema of the arguments. Defaults to `{}`. */\n parameters?: Record<string, unknown> | undefined;\n}\n\n/**\n * Role of a chat message.\n */\nexport type MessageRole = 'system' | 'user' | 'assistant' | 'tool';\n\n/**\n * Tool call in the internal format.\n */\nexport interface ToolCall {\n /** Identifier assigned by the API. */\n id?: string | undefined;\n /** Name of the tool. */\n tool_name: string;\n /** Parsed arguments. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Tool call returned by `chatCompletion`. It carries both the OpenAI shape\n * (`type`, `function`) and the internal shape (`tool_name`, `arguments`).\n */\nexport interface ResponseToolCall {\n /** Identifier assigned by the API. A `tool` message answers the call with it. */\n id: string;\n /** Always `function`. */\n type: 'function';\n /** The call in the OpenAI shape: function name and raw JSON arguments. An empty argument string is sent as `{}`. */\n function: {\n /** Name of the tool to call. */\n name: string;\n /** Arguments as JSON text. */\n arguments: string;\n };\n /** Name of the tool, same as `function.name`. */\n tool_name: string;\n /** Parsed arguments. JSON that is not an object becomes `{}`. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Partial tool call emitted while streaming. `arguments` holds the raw JSON fragment.\n */\nexport interface StreamToolCall {\n /** Identifier assigned by the API. Only the first delta of a call carries it; later deltas have `''`. */\n id: string;\n /** Always `function`. */\n type: 'function';\n /** The delta in the OpenAI shape. `arguments` holds only this delta's JSON fragment. */\n function: {\n /** Name of the tool to call. */\n name: string;\n /** Arguments as JSON text. */\n arguments: string;\n };\n /** Name of the tool when this delta carries it, otherwise `''`. */\n tool_name: string;\n /** Raw JSON fragment of this delta. Concatenate the fragments of a call to get its arguments. */\n arguments: string;\n}\n\n/**\n * Token usage reported by the API.\n */\nexport interface TokenUsage {\n /** Tokens in the generated reply. */\n completion_tokens: number;\n /** Tokens in the request. */\n prompt_tokens: number;\n /** Prompt tokens served from the provider's context cache (reported by DeepSeek). */\n prompt_cache_hit_tokens?: number | undefined;\n /** Prompt tokens not served from the cache. */\n prompt_cache_miss_tokens?: number | undefined;\n /** Sum of prompt and completion tokens. */\n total_tokens: number;\n}\n\n/**\n * Chat message accepted as input. It matches the agent message contract in\n * `@astack-tech/components`, so agent histories can be passed directly.\n */\nexport interface Message {\n /** One of `system`, `user`, `assistant` or `tool` (see `MessageRole`). */\n role: string;\n /** Text content. Omitted content is sent as no content. */\n content?: string | undefined;\n /** Accepted for compatibility. Assistant tool calls are not forwarded to the API. */\n tool_calls?: readonly unknown[] | undefined;\n /** Identifier of the call that a `tool` message answers. */\n tool_call_id?: string | undefined;\n /** Ignored on input. Present so agent histories can be passed unchanged. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Assistant message returned by `chatCompletion`.\n */\nexport interface AssistantMessage {\n /** Always `assistant`. */\n role: 'assistant';\n /** Reply text. Empty when the model only calls tools. */\n content: string;\n /** Tool calls requested by the model, if any. */\n tool_calls?: ResponseToolCall[] | undefined;\n /** Token usage of this request, when the API reports it. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Chunk yielded by `streamChatCompletion`.\n */\nexport interface StreamChunk {\n /** Always `assistant`. */\n role: 'assistant';\n /** Text delta. Empty in chunks that carry tool calls or usage. */\n content: string;\n /** Tool-call deltas in this chunk. */\n tool_calls?: StreamToolCall[] | undefined;\n /** Token usage. Sent once, in the final chunk, when the API reports it. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Per-call options.\n */\nexport interface ChatCompletionOptions {\n /** Tools for this call only. They replace the configured tools. */\n temporaryTools?: unknown[] | undefined;\n}\n\ntype ChatMessageParam = OpenAI.Chat.ChatCompletionMessageParam;\ntype ChatTool = OpenAI.Chat.ChatCompletionTool;\n\ninterface ApiUsage {\n completion_tokens?: number;\n prompt_tokens?: number;\n total_tokens?: number;\n prompt_cache_hit_tokens?: number;\n prompt_cache_miss_tokens?: number;\n}\n\nconst DEFAULT_BASE_URL = 'https://api.openai.com/v1';\nconst DEFAULT_NAME = 'The model API';\nconst DEFAULT_TEMPERATURE = 0.7;\nconst FALLBACK_TEXT = 'API call failed';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isOptionalString(value: unknown): value is string | undefined {\n return value === undefined || typeof value === 'string';\n}\n\nfunction isCount(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value);\n}\n\nfunction isTokenUsage(value: unknown): value is TokenUsage {\n return (\n isRecord(value) &&\n isCount(value['completion_tokens']) &&\n isCount(value['prompt_tokens']) &&\n isCount(value['total_tokens']) &&\n (value['prompt_cache_hit_tokens'] === undefined || isCount(value['prompt_cache_hit_tokens'])) &&\n (value['prompt_cache_miss_tokens'] === undefined || isCount(value['prompt_cache_miss_tokens']))\n );\n}\n\n/** Whether `value` is a `Message`, checked field by field. The role is checked when the message is sent. */\nfunction isMessage(value: unknown): value is Message {\n return (\n isRecord(value) &&\n typeof value['role'] === 'string' &&\n isOptionalString(value['content']) &&\n (value['tool_calls'] === undefined || Array.isArray(value['tool_calls'])) &&\n isOptionalString(value['tool_call_id']) &&\n (value['usage'] === undefined || isTokenUsage(value['usage']))\n );\n}\n\nfunction isMessageList(value: unknown): value is Message[] {\n return Array.isArray(value) && value.every(item => isMessage(item));\n}\n\n/**\n * Converts a `{ name, description, parameters }` object to the OpenAI tool format.\n * Objects already in the OpenAI format are returned unchanged.\n */\nfunction toApiTool(tool: unknown): ChatTool {\n const raw = isRecord(tool) ? tool : {};\n const definition = raw['function'];\n if (raw['type'] === 'function' && isRecord(definition)) {\n const { name, description, parameters, strict } = definition;\n return {\n type: 'function',\n function: {\n name: typeof name === 'string' ? name : '',\n ...(typeof description === 'string' ? { description } : {}),\n ...(isRecord(parameters) ? { parameters } : {}),\n ...(typeof strict === 'boolean' ? { strict } : {}),\n },\n };\n }\n const { name, description, parameters } = raw;\n return {\n type: 'function',\n function: {\n name: typeof name === 'string' ? name : '',\n description: typeof description === 'string' ? description : '',\n parameters: isRecord(parameters) ? parameters : {},\n },\n };\n}\n\n/**\n * Parses tool-call arguments. Anything that is not a JSON object becomes `{}`.\n */\nfunction parseArguments(raw: string): Record<string, unknown> {\n if (raw.trim().length === 0) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n return isRecord(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\nfunction mapUsage(usage: ApiUsage): TokenUsage {\n return {\n completion_tokens: usage.completion_tokens ?? 0,\n prompt_tokens: usage.prompt_tokens ?? 0,\n total_tokens: usage.total_tokens ?? 0,\n ...(usage.prompt_cache_hit_tokens === undefined\n ? {}\n : { prompt_cache_hit_tokens: usage.prompt_cache_hit_tokens }),\n ...(usage.prompt_cache_miss_tokens === undefined\n ? {}\n : { prompt_cache_miss_tokens: usage.prompt_cache_miss_tokens }),\n };\n}\n\n/**\n * Model provider for any API that speaks the OpenAI chat completions protocol:\n * OpenAI itself, and the compatible endpoints of DeepSeek, Qwen, Moonshot, Ollama,\n * vLLM and others. Set `baseURL` and `model` for the provider you use.\n *\n * Ports:\n * - `prompt` (in): a text prompt. The answer is sent on `completion`.\n * - `messages` (in): a list of chat messages. The reply is sent on `message`.\n */\nclass OpenAICompatible extends Component {\n private readonly client: OpenAI;\n private readonly model: string;\n private readonly temperature: number;\n private readonly maxTokens: number | undefined;\n private readonly topP: number | undefined;\n private readonly systemPrompt: string | undefined;\n private readonly tools: unknown[] | undefined;\n private readonly verbose: boolean;\n private readonly name: string;\n\n constructor(config: OpenAICompatibleConfig) {\n super({});\n\n this.client = new OpenAI({\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n dangerouslyAllowBrowser: config.dangerouslyAllowBrowser ?? false,\n });\n\n this.model = config.model;\n this.name = config.name ?? DEFAULT_NAME;\n this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;\n this.maxTokens = config.maxTokens;\n this.topP = config.topP;\n this.systemPrompt = config.systemPrompt;\n this.verbose = config.verbose ?? false;\n this.tools =\n config.rawTools !== undefined && config.rawTools.length > 0\n ? config.rawTools.map(toApiTool)\n : config.tools;\n\n Component.Port.I('prompt').attach(this);\n Component.Port.I('messages').attach(this);\n Component.Port.O('completion').attach(this);\n Component.Port.O('message').attach(this);\n }\n\n /**\n * Generates a completion for a single prompt.\n *\n * @returns The text answer, or the JSON-encoded assistant message when it contains tool calls.\n */\n async generateCompletion(prompt: string): Promise<string> {\n const response = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: [...this.systemMessages(), { role: 'user', content: prompt }],\n ...this.toolsParam(this.tools),\n });\n\n const message = this.firstMessage(response);\n if (message.tool_calls !== undefined && message.tool_calls.length > 0) {\n return JSON.stringify(message);\n }\n return message.content ?? '';\n }\n\n /**\n * Sends a conversation and returns the assistant reply.\n */\n async chatCompletion(\n messages: Message[],\n options?: ChatCompletionOptions\n ): Promise<AssistantMessage> {\n const response = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: this.formatMessages(messages),\n ...this.toolsParam(options?.temporaryTools ?? this.tools),\n });\n\n const message = this.firstMessage(response);\n const result: AssistantMessage = { role: 'assistant', content: message.content ?? '' };\n\n if (response.usage !== undefined) {\n result.usage = mapUsage(response.usage);\n }\n\n if (message.tool_calls !== undefined && message.tool_calls.length > 0) {\n result.tool_calls = message.tool_calls.map(toolCall => {\n if (toolCall.type !== 'function') {\n throw new Error(`Unsupported tool call type: ${toolCall.type}`);\n }\n const rawArguments = toolCall.function.arguments;\n return {\n id: toolCall.id,\n type: 'function',\n function: {\n name: toolCall.function.name,\n arguments: rawArguments.length > 0 ? rawArguments : '{}',\n },\n tool_name: toolCall.function.name,\n arguments: parseArguments(rawArguments),\n };\n });\n }\n\n return result;\n }\n\n /**\n * Streams the assistant reply. Each chunk holds a text delta, tool-call deltas, or usage.\n */\n async *streamChatCompletion(\n messages: Message[],\n options?: ChatCompletionOptions\n ): AsyncGenerator<StreamChunk> {\n const stream = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: this.formatMessages(messages),\n ...this.toolsParam(options?.temporaryTools ?? this.tools),\n stream: true,\n });\n\n for await (const chunk of stream) {\n const delta = chunk.choices[0]?.delta;\n\n const text = delta?.content ?? '';\n if (text !== '') {\n yield { role: 'assistant', content: text };\n }\n\n if (delta?.tool_calls) {\n yield {\n role: 'assistant',\n content: '',\n tool_calls: delta.tool_calls.map(toolCall => {\n const name = toolCall.function?.name ?? '';\n const args = toolCall.function?.arguments ?? '';\n return {\n id: toolCall.id ?? '',\n type: 'function',\n function: { name, arguments: args },\n tool_name: name,\n arguments: args,\n };\n }),\n };\n }\n\n if (chunk.usage) {\n yield { role: 'assistant', content: '', usage: mapUsage(chunk.usage) };\n }\n }\n }\n\n /**\n * Runs the model outside a pipeline.\n *\n * @param input - A prompt string or a list of messages.\n */\n override run(input: string | Message[]): Promise<string | AssistantMessage> {\n return typeof input === 'string' ? this.generateCompletion(input) : this.chatCompletion(input);\n }\n\n /**\n * Connects the `prompt` and `messages` input ports to the model.\n * API errors produce a fallback value so the pipeline keeps flowing. Input of the wrong\n * type fails the pipeline run with a `TypeError` before the API is called.\n */\n override _transform($i: Port, $o: Port): void {\n $i('prompt').receive(prompt => {\n if (typeof prompt !== 'string') {\n throw new TypeError(`${this.name}: the prompt port expects a string.`);\n }\n return this.generateCompletion(prompt).then(\n completion => {\n $o('completion').send(completion);\n },\n (error: unknown) => {\n this.logError(error);\n $o('completion').send(FALLBACK_TEXT);\n }\n );\n });\n\n $i('messages').receive(messages => {\n if (!isMessageList(messages)) {\n throw new TypeError(`${this.name}: the messages port expects an array of messages.`);\n }\n return this.chatCompletion(messages).then(\n message => {\n $o('message').send(message);\n },\n (error: unknown) => {\n this.logError(error);\n $o('message').send({ role: 'assistant', content: FALLBACK_TEXT });\n }\n );\n });\n }\n\n private baseRequest(): {\n model: string;\n temperature: number;\n max_tokens?: number;\n top_p?: number;\n } {\n return {\n model: this.model,\n temperature: this.temperature,\n ...(this.maxTokens === undefined ? {} : { max_tokens: this.maxTokens }),\n ...(this.topP === undefined ? {} : { top_p: this.topP }),\n };\n }\n\n private toolsParam(tools: unknown[] | undefined): { tools?: ChatTool[] } {\n return tools !== undefined && tools.length > 0 ? { tools: tools.map(toApiTool) } : {};\n }\n\n private systemMessages(): ChatMessageParam[] {\n return this.systemPrompt === undefined || this.systemPrompt === ''\n ? []\n : [{ role: 'system', content: this.systemPrompt }];\n }\n\n /**\n * Converts input messages to API messages. Assistant `tool_calls` are not forwarded:\n * agents send tool results back as user messages, and the API rejects assistant tool\n * calls that are not followed by tool messages.\n */\n private formatMessages(messages: Message[]): ChatMessageParam[] {\n return [...this.systemMessages(), ...messages.map(message => this.toApiMessage(message))];\n }\n\n /**\n * Converts one input message to its API shape.\n *\n * @throws Error when the role is not one of `MessageRole`, or a `tool` message has no\n * `tool_call_id`. The API would reject both.\n */\n private toApiMessage(message: Message): ChatMessageParam {\n const content = message.content ?? '';\n switch (message.role) {\n case 'system':\n return { role: 'system', content };\n case 'user':\n return { role: 'user', content };\n case 'assistant':\n return message.content === undefined\n ? { role: 'assistant' }\n : { role: 'assistant', content: message.content };\n case 'tool':\n if (message.tool_call_id === undefined || message.tool_call_id === '') {\n throw new Error(\n `${this.name}: a tool message needs the tool_call_id of the call it answers.`\n );\n }\n return { role: 'tool', content, tool_call_id: message.tool_call_id };\n default:\n throw new Error(\n `${this.name}: unsupported message role '${message.role}'. Use system, user, assistant or tool.`\n );\n }\n }\n\n private firstMessage(response: OpenAI.Chat.ChatCompletion): OpenAI.Chat.ChatCompletionMessage {\n const choice = response.choices[0];\n if (choice === undefined) {\n throw new Error(`${this.name} returned no choices.`);\n }\n return choice.message;\n }\n\n private logError(error: unknown): void {\n if (this.verbose) {\n // eslint-disable-next-line no-console -- opt-in diagnostics controlled by `verbose`.\n console.error(`[${this.name}] API call failed:`, error);\n }\n }\n}\n\nexport default OpenAICompatible;\n","import OpenAICompatible from '../openai-compatible';\nimport type { OpenAICompatibleConfig } from '../openai-compatible';\n\nexport type {\n AssistantMessage,\n ChatCompletionOptions,\n Message,\n MessageRole,\n RawTool,\n ResponseToolCall,\n StreamChunk,\n StreamToolCall,\n TokenUsage,\n ToolCall,\n} from '../openai-compatible';\n\nconst DEFAULT_BASE_URL = 'https://api.deepseek.com/v1';\nconst DEFAULT_MODEL = 'deepseek-chat';\n\n/**\n * Options for the DeepSeek model provider. They are the options of\n * `OpenAICompatible`, with DeepSeek's endpoint and model as defaults.\n */\nexport interface DeepseekConfig extends Omit<OpenAICompatibleConfig, 'model' | 'name'> {\n /** Model name. Defaults to `deepseek-chat`. */\n model?: string | undefined;\n /** API base URL. Defaults to `https://api.deepseek.com/v1`. */\n baseURL?: string | undefined;\n}\n\n/**\n * DeepSeek model provider: `OpenAICompatible` set up for the DeepSeek API.\n *\n * Ports:\n * - `prompt` (in): a text prompt. The answer is sent on `completion`.\n * - `messages` (in): a list of chat messages. The reply is sent on `message`.\n */\nclass Deepseek extends OpenAICompatible {\n constructor(config: DeepseekConfig) {\n super({\n ...config,\n model: config.model ?? DEFAULT_MODEL,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n name: 'DeepSeek',\n });\n }\n}\n\nexport default Deepseek;\n"],"mappings":"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","names":["name","description","parameters","OpenAI","DEFAULT_BASE_URL"]}
package/dist/index.js CHANGED
@@ -21,6 +21,21 @@ var FALLBACK_TEXT = "API call failed";
21
21
  function isRecord(value) {
22
22
  return typeof value === "object" && value !== null && !Array.isArray(value);
23
23
  }
24
+ function isOptionalString(value) {
25
+ return value === void 0 || typeof value === "string";
26
+ }
27
+ function isCount(value) {
28
+ return typeof value === "number" && Number.isFinite(value);
29
+ }
30
+ function isTokenUsage(value) {
31
+ return isRecord(value) && isCount(value["completion_tokens"]) && isCount(value["prompt_tokens"]) && isCount(value["total_tokens"]) && (value["prompt_cache_hit_tokens"] === void 0 || isCount(value["prompt_cache_hit_tokens"])) && (value["prompt_cache_miss_tokens"] === void 0 || isCount(value["prompt_cache_miss_tokens"]));
32
+ }
33
+ function isMessage(value) {
34
+ return isRecord(value) && typeof value["role"] === "string" && isOptionalString(value["content"]) && (value["tool_calls"] === void 0 || Array.isArray(value["tool_calls"])) && isOptionalString(value["tool_call_id"]) && (value["usage"] === void 0 || isTokenUsage(value["usage"]));
35
+ }
36
+ function isMessageList(value) {
37
+ return Array.isArray(value) && value.every((item) => isMessage(item));
38
+ }
24
39
  function toApiTool(tool) {
25
40
  const raw = isRecord(tool) ? tool : {};
26
41
  const definition = raw["function"];
@@ -195,11 +210,15 @@ var OpenAICompatible = class extends Component {
195
210
  }
196
211
  /**
197
212
  * Connects the `prompt` and `messages` input ports to the model.
198
- * API errors produce a fallback value so the pipeline keeps flowing.
213
+ * API errors produce a fallback value so the pipeline keeps flowing. Input of the wrong
214
+ * type fails the pipeline run with a `TypeError` before the API is called.
199
215
  */
200
216
  _transform($i, $o) {
201
217
  $i("prompt").receive((prompt) => {
202
- this.generateCompletion(prompt).then(
218
+ if (typeof prompt !== "string") {
219
+ throw new TypeError(`${this.name}: the prompt port expects a string.`);
220
+ }
221
+ return this.generateCompletion(prompt).then(
203
222
  (completion) => {
204
223
  $o("completion").send(completion);
205
224
  },
@@ -210,7 +229,10 @@ var OpenAICompatible = class extends Component {
210
229
  );
211
230
  });
212
231
  $i("messages").receive((messages) => {
213
- this.chatCompletion(messages).then(
232
+ if (!isMessageList(messages)) {
233
+ throw new TypeError(`${this.name}: the messages port expects an array of messages.`);
234
+ }
235
+ return this.chatCompletion(messages).then(
214
236
  (message) => {
215
237
  $o("message").send(message);
216
238
  },
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/model-provider/index.ts","../src/model-provider/openai-compatible/index.ts","../src/model-provider/deepseek/index.ts"],"sourcesContent":["export {\n default as Deepseek,\n type AssistantMessage as DeepseekAssistantMessage,\n type ChatCompletionOptions as DeepseekChatCompletionOptions,\n type DeepseekConfig,\n type Message as DeepseekMessage,\n type MessageRole as DeepseekMessageRole,\n type RawTool as DeepseekRawTool,\n type ResponseToolCall as DeepseekResponseToolCall,\n type StreamChunk as DeepseekStreamChunk,\n type StreamToolCall as DeepseekStreamToolCall,\n type TokenUsage as DeepseekTokenUsage,\n type ToolCall as DeepseekToolCall,\n} from './deepseek';\nexport {\n default as OpenAICompatible,\n type AssistantMessage,\n type ChatCompletionOptions,\n type Message,\n type MessageRole,\n type OpenAICompatibleConfig,\n type RawTool,\n type ResponseToolCall,\n type StreamChunk,\n type StreamToolCall,\n type TokenUsage,\n type ToolCall,\n} from './openai-compatible';\n","import { Component } from '@astack-tech/core';\nimport type { Port } from '@astack-tech/core';\nimport OpenAI from 'openai';\n\n/**\n * Options for a provider that speaks the OpenAI chat completions API.\n */\nexport interface OpenAICompatibleConfig {\n /** API key of the provider. */\n apiKey: string;\n /** Model name, for example `gpt-4o-mini`, `qwen-plus` or `llama3.1`. */\n model: string;\n /** API base URL. Defaults to `https://api.openai.com/v1`. */\n baseURL?: string | undefined;\n /** Provider name used in errors and logs. Defaults to `The model API`. */\n name?: string | undefined;\n /** Sampling temperature. Defaults to `0.7`. */\n temperature?: number | undefined;\n /** Maximum number of generated tokens. */\n maxTokens?: number | undefined;\n /** Nucleus sampling probability. */\n topP?: number | undefined;\n /** System prompt added before every request. */\n systemPrompt?: string | undefined;\n /**\n * Tools given as `{ name, description, parameters }` objects.\n * When this list is not empty, it takes precedence over `tools`.\n */\n rawTools?: RawTool[] | undefined;\n /** Tools in the OpenAI-compatible `{ type: 'function', function }` format. */\n tools?: unknown[] | undefined;\n /** Allows the OpenAI client to run in a browser. Defaults to `false`. */\n dangerouslyAllowBrowser?: boolean | undefined;\n /** Logs API errors to the console. Defaults to `false`. */\n verbose?: boolean | undefined;\n}\n\n/**\n * Tool description accepted by `rawTools` and `temporaryTools`.\n */\nexport interface RawTool {\n /** Unique tool name. */\n name: string;\n /** Description that tells the model when to use the tool. */\n description: string;\n /** JSON Schema of the arguments. Defaults to `{}`. */\n parameters?: Record<string, unknown> | undefined;\n}\n\n/**\n * Role of a chat message.\n */\nexport type MessageRole = 'system' | 'user' | 'assistant' | 'tool';\n\n/**\n * Tool call in the internal format.\n */\nexport interface ToolCall {\n /** Identifier assigned by the API. */\n id?: string | undefined;\n /** Name of the tool. */\n tool_name: string;\n /** Parsed arguments. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Tool call returned by `chatCompletion`. It carries both the OpenAI shape\n * (`type`, `function`) and the internal shape (`tool_name`, `arguments`).\n */\nexport interface ResponseToolCall {\n /** Identifier assigned by the API. A `tool` message answers the call with it. */\n id: string;\n /** Always `function`. */\n type: 'function';\n /** The call in the OpenAI shape: function name and raw JSON arguments. An empty argument string is sent as `{}`. */\n function: {\n /** Name of the tool to call. */\n name: string;\n /** Arguments as JSON text. */\n arguments: string;\n };\n /** Name of the tool, same as `function.name`. */\n tool_name: string;\n /** Parsed arguments. JSON that is not an object becomes `{}`. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Partial tool call emitted while streaming. `arguments` holds the raw JSON fragment.\n */\nexport interface StreamToolCall {\n /** Identifier assigned by the API. Only the first delta of a call carries it; later deltas have `''`. */\n id: string;\n /** Always `function`. */\n type: 'function';\n /** The delta in the OpenAI shape. `arguments` holds only this delta's JSON fragment. */\n function: {\n /** Name of the tool to call. */\n name: string;\n /** Arguments as JSON text. */\n arguments: string;\n };\n /** Name of the tool when this delta carries it, otherwise `''`. */\n tool_name: string;\n /** Raw JSON fragment of this delta. Concatenate the fragments of a call to get its arguments. */\n arguments: string;\n}\n\n/**\n * Token usage reported by the API.\n */\nexport interface TokenUsage {\n /** Tokens in the generated reply. */\n completion_tokens: number;\n /** Tokens in the request. */\n prompt_tokens: number;\n /** Prompt tokens served from the provider's context cache (reported by DeepSeek). */\n prompt_cache_hit_tokens?: number | undefined;\n /** Prompt tokens not served from the cache. */\n prompt_cache_miss_tokens?: number | undefined;\n /** Sum of prompt and completion tokens. */\n total_tokens: number;\n}\n\n/**\n * Chat message accepted as input. It matches the agent message contract in\n * `@astack-tech/components`, so agent histories can be passed directly.\n */\nexport interface Message {\n /** One of `system`, `user`, `assistant` or `tool` (see `MessageRole`). */\n role: string;\n /** Text content. Omitted content is sent as no content. */\n content?: string | undefined;\n /** Accepted for compatibility. Assistant tool calls are not forwarded to the API. */\n tool_calls?: readonly unknown[] | undefined;\n /** Identifier of the call that a `tool` message answers. */\n tool_call_id?: string | undefined;\n /** Ignored on input. Present so agent histories can be passed unchanged. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Assistant message returned by `chatCompletion`.\n */\nexport interface AssistantMessage {\n /** Always `assistant`. */\n role: 'assistant';\n /** Reply text. Empty when the model only calls tools. */\n content: string;\n /** Tool calls requested by the model, if any. */\n tool_calls?: ResponseToolCall[] | undefined;\n /** Token usage of this request, when the API reports it. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Chunk yielded by `streamChatCompletion`.\n */\nexport interface StreamChunk {\n /** Always `assistant`. */\n role: 'assistant';\n /** Text delta. Empty in chunks that carry tool calls or usage. */\n content: string;\n /** Tool-call deltas in this chunk. */\n tool_calls?: StreamToolCall[] | undefined;\n /** Token usage. Sent once, in the final chunk, when the API reports it. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Per-call options.\n */\nexport interface ChatCompletionOptions {\n /** Tools for this call only. They replace the configured tools. */\n temporaryTools?: unknown[] | undefined;\n}\n\ntype ChatMessageParam = OpenAI.Chat.ChatCompletionMessageParam;\ntype ChatTool = OpenAI.Chat.ChatCompletionTool;\n\ninterface ApiUsage {\n completion_tokens?: number;\n prompt_tokens?: number;\n total_tokens?: number;\n prompt_cache_hit_tokens?: number;\n prompt_cache_miss_tokens?: number;\n}\n\nconst DEFAULT_BASE_URL = 'https://api.openai.com/v1';\nconst DEFAULT_NAME = 'The model API';\nconst DEFAULT_TEMPERATURE = 0.7;\nconst FALLBACK_TEXT = 'API call failed';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Converts a `{ name, description, parameters }` object to the OpenAI tool format.\n * Objects already in the OpenAI format are returned unchanged.\n */\nfunction toApiTool(tool: unknown): ChatTool {\n const raw = isRecord(tool) ? tool : {};\n const definition = raw['function'];\n if (raw['type'] === 'function' && isRecord(definition)) {\n const { name, description, parameters, strict } = definition;\n return {\n type: 'function',\n function: {\n name: typeof name === 'string' ? name : '',\n ...(typeof description === 'string' ? { description } : {}),\n ...(isRecord(parameters) ? { parameters } : {}),\n ...(typeof strict === 'boolean' ? { strict } : {}),\n },\n };\n }\n const { name, description, parameters } = raw;\n return {\n type: 'function',\n function: {\n name: typeof name === 'string' ? name : '',\n description: typeof description === 'string' ? description : '',\n parameters: isRecord(parameters) ? parameters : {},\n },\n };\n}\n\n/**\n * Parses tool-call arguments. Anything that is not a JSON object becomes `{}`.\n */\nfunction parseArguments(raw: string): Record<string, unknown> {\n if (raw.trim().length === 0) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n return isRecord(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\nfunction mapUsage(usage: ApiUsage): TokenUsage {\n return {\n completion_tokens: usage.completion_tokens ?? 0,\n prompt_tokens: usage.prompt_tokens ?? 0,\n total_tokens: usage.total_tokens ?? 0,\n ...(usage.prompt_cache_hit_tokens === undefined\n ? {}\n : { prompt_cache_hit_tokens: usage.prompt_cache_hit_tokens }),\n ...(usage.prompt_cache_miss_tokens === undefined\n ? {}\n : { prompt_cache_miss_tokens: usage.prompt_cache_miss_tokens }),\n };\n}\n\n/**\n * Model provider for any API that speaks the OpenAI chat completions protocol:\n * OpenAI itself, and the compatible endpoints of DeepSeek, Qwen, Moonshot, Ollama,\n * vLLM and others. Set `baseURL` and `model` for the provider you use.\n *\n * Ports:\n * - `prompt` (in): a text prompt. The answer is sent on `completion`.\n * - `messages` (in): a list of chat messages. The reply is sent on `message`.\n */\nclass OpenAICompatible extends Component {\n private readonly client: OpenAI;\n private readonly model: string;\n private readonly temperature: number;\n private readonly maxTokens: number | undefined;\n private readonly topP: number | undefined;\n private readonly systemPrompt: string | undefined;\n private readonly tools: unknown[] | undefined;\n private readonly verbose: boolean;\n private readonly name: string;\n\n constructor(config: OpenAICompatibleConfig) {\n super({});\n\n this.client = new OpenAI({\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n dangerouslyAllowBrowser: config.dangerouslyAllowBrowser ?? false,\n });\n\n this.model = config.model;\n this.name = config.name ?? DEFAULT_NAME;\n this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;\n this.maxTokens = config.maxTokens;\n this.topP = config.topP;\n this.systemPrompt = config.systemPrompt;\n this.verbose = config.verbose ?? false;\n this.tools =\n config.rawTools !== undefined && config.rawTools.length > 0\n ? config.rawTools.map(toApiTool)\n : config.tools;\n\n Component.Port.I('prompt').attach(this);\n Component.Port.I('messages').attach(this);\n Component.Port.O('completion').attach(this);\n Component.Port.O('message').attach(this);\n }\n\n /**\n * Generates a completion for a single prompt.\n *\n * @returns The text answer, or the JSON-encoded assistant message when it contains tool calls.\n */\n async generateCompletion(prompt: string): Promise<string> {\n const response = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: [...this.systemMessages(), { role: 'user', content: prompt }],\n ...this.toolsParam(this.tools),\n });\n\n const message = this.firstMessage(response);\n if (message.tool_calls !== undefined && message.tool_calls.length > 0) {\n return JSON.stringify(message);\n }\n return message.content ?? '';\n }\n\n /**\n * Sends a conversation and returns the assistant reply.\n */\n async chatCompletion(\n messages: Message[],\n options?: ChatCompletionOptions\n ): Promise<AssistantMessage> {\n const response = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: this.formatMessages(messages),\n ...this.toolsParam(options?.temporaryTools ?? this.tools),\n });\n\n const message = this.firstMessage(response);\n const result: AssistantMessage = { role: 'assistant', content: message.content ?? '' };\n\n if (response.usage !== undefined) {\n result.usage = mapUsage(response.usage);\n }\n\n if (message.tool_calls !== undefined && message.tool_calls.length > 0) {\n result.tool_calls = message.tool_calls.map(toolCall => {\n if (toolCall.type !== 'function') {\n throw new Error(`Unsupported tool call type: ${toolCall.type}`);\n }\n const rawArguments = toolCall.function.arguments;\n return {\n id: toolCall.id,\n type: 'function',\n function: {\n name: toolCall.function.name,\n arguments: rawArguments.length > 0 ? rawArguments : '{}',\n },\n tool_name: toolCall.function.name,\n arguments: parseArguments(rawArguments),\n };\n });\n }\n\n return result;\n }\n\n /**\n * Streams the assistant reply. Each chunk holds a text delta, tool-call deltas, or usage.\n */\n async *streamChatCompletion(\n messages: Message[],\n options?: ChatCompletionOptions\n ): AsyncGenerator<StreamChunk> {\n const stream = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: this.formatMessages(messages),\n ...this.toolsParam(options?.temporaryTools ?? this.tools),\n stream: true,\n });\n\n for await (const chunk of stream) {\n const delta = chunk.choices[0]?.delta;\n\n const text = delta?.content ?? '';\n if (text !== '') {\n yield { role: 'assistant', content: text };\n }\n\n if (delta?.tool_calls) {\n yield {\n role: 'assistant',\n content: '',\n tool_calls: delta.tool_calls.map(toolCall => {\n const name = toolCall.function?.name ?? '';\n const args = toolCall.function?.arguments ?? '';\n return {\n id: toolCall.id ?? '',\n type: 'function',\n function: { name, arguments: args },\n tool_name: name,\n arguments: args,\n };\n }),\n };\n }\n\n if (chunk.usage) {\n yield { role: 'assistant', content: '', usage: mapUsage(chunk.usage) };\n }\n }\n }\n\n /**\n * Runs the model outside a pipeline.\n *\n * @param input - A prompt string or a list of messages.\n */\n override run(input: string | Message[]): Promise<string | AssistantMessage> {\n return typeof input === 'string' ? this.generateCompletion(input) : this.chatCompletion(input);\n }\n\n /**\n * Connects the `prompt` and `messages` input ports to the model.\n * API errors produce a fallback value so the pipeline keeps flowing.\n */\n override _transform($i: Port, $o: Port): void {\n $i('prompt').receive((prompt: string) => {\n this.generateCompletion(prompt).then(\n completion => {\n $o('completion').send(completion);\n },\n (error: unknown) => {\n this.logError(error);\n $o('completion').send(FALLBACK_TEXT);\n }\n );\n });\n\n $i('messages').receive((messages: Message[]) => {\n this.chatCompletion(messages).then(\n message => {\n $o('message').send(message);\n },\n (error: unknown) => {\n this.logError(error);\n $o('message').send({ role: 'assistant', content: FALLBACK_TEXT });\n }\n );\n });\n }\n\n private baseRequest(): {\n model: string;\n temperature: number;\n max_tokens?: number;\n top_p?: number;\n } {\n return {\n model: this.model,\n temperature: this.temperature,\n ...(this.maxTokens === undefined ? {} : { max_tokens: this.maxTokens }),\n ...(this.topP === undefined ? {} : { top_p: this.topP }),\n };\n }\n\n private toolsParam(tools: unknown[] | undefined): { tools?: ChatTool[] } {\n return tools !== undefined && tools.length > 0 ? { tools: tools.map(toApiTool) } : {};\n }\n\n private systemMessages(): ChatMessageParam[] {\n return this.systemPrompt === undefined || this.systemPrompt === ''\n ? []\n : [{ role: 'system', content: this.systemPrompt }];\n }\n\n /**\n * Converts input messages to API messages. Assistant `tool_calls` are not forwarded:\n * agents send tool results back as user messages, and the API rejects assistant tool\n * calls that are not followed by tool messages.\n */\n private formatMessages(messages: Message[]): ChatMessageParam[] {\n return [...this.systemMessages(), ...messages.map(message => this.toApiMessage(message))];\n }\n\n /**\n * Converts one input message to its API shape.\n *\n * @throws Error when the role is not one of `MessageRole`, or a `tool` message has no\n * `tool_call_id`. The API would reject both.\n */\n private toApiMessage(message: Message): ChatMessageParam {\n const content = message.content ?? '';\n switch (message.role) {\n case 'system':\n return { role: 'system', content };\n case 'user':\n return { role: 'user', content };\n case 'assistant':\n return message.content === undefined\n ? { role: 'assistant' }\n : { role: 'assistant', content: message.content };\n case 'tool':\n if (message.tool_call_id === undefined || message.tool_call_id === '') {\n throw new Error(\n `${this.name}: a tool message needs the tool_call_id of the call it answers.`\n );\n }\n return { role: 'tool', content, tool_call_id: message.tool_call_id };\n default:\n throw new Error(\n `${this.name}: unsupported message role '${message.role}'. Use system, user, assistant or tool.`\n );\n }\n }\n\n private firstMessage(response: OpenAI.Chat.ChatCompletion): OpenAI.Chat.ChatCompletionMessage {\n const choice = response.choices[0];\n if (choice === undefined) {\n throw new Error(`${this.name} returned no choices.`);\n }\n return choice.message;\n }\n\n private logError(error: unknown): void {\n if (this.verbose) {\n // eslint-disable-next-line no-console -- opt-in diagnostics controlled by `verbose`.\n console.error(`[${this.name}] API call failed:`, error);\n }\n }\n}\n\nexport default OpenAICompatible;\n","import OpenAICompatible from '../openai-compatible';\nimport type { OpenAICompatibleConfig } from '../openai-compatible';\n\nexport type {\n AssistantMessage,\n ChatCompletionOptions,\n Message,\n MessageRole,\n RawTool,\n ResponseToolCall,\n StreamChunk,\n StreamToolCall,\n TokenUsage,\n ToolCall,\n} from '../openai-compatible';\n\nconst DEFAULT_BASE_URL = 'https://api.deepseek.com/v1';\nconst DEFAULT_MODEL = 'deepseek-chat';\n\n/**\n * Options for the DeepSeek model provider. They are the options of\n * `OpenAICompatible`, with DeepSeek's endpoint and model as defaults.\n */\nexport interface DeepseekConfig extends Omit<OpenAICompatibleConfig, 'model' | 'name'> {\n /** Model name. Defaults to `deepseek-chat`. */\n model?: string | undefined;\n /** API base URL. Defaults to `https://api.deepseek.com/v1`. */\n baseURL?: string | undefined;\n}\n\n/**\n * DeepSeek model provider: `OpenAICompatible` set up for the DeepSeek API.\n *\n * Ports:\n * - `prompt` (in): a text prompt. The answer is sent on `completion`.\n * - `messages` (in): a list of chat messages. The reply is sent on `message`.\n */\nclass Deepseek extends OpenAICompatible {\n constructor(config: DeepseekConfig) {\n super({\n ...config,\n model: config.model ?? DEFAULT_MODEL,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n name: 'DeepSeek',\n });\n }\n}\n\nexport default Deepseek;\n"],"mappings":"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","names":["name","description","parameters","DEFAULT_BASE_URL"]}
1
+ {"version":3,"sources":["../src/model-provider/index.ts","../src/model-provider/openai-compatible/index.ts","../src/model-provider/deepseek/index.ts"],"sourcesContent":["export {\n default as Deepseek,\n type AssistantMessage as DeepseekAssistantMessage,\n type ChatCompletionOptions as DeepseekChatCompletionOptions,\n type DeepseekConfig,\n type Message as DeepseekMessage,\n type MessageRole as DeepseekMessageRole,\n type RawTool as DeepseekRawTool,\n type ResponseToolCall as DeepseekResponseToolCall,\n type StreamChunk as DeepseekStreamChunk,\n type StreamToolCall as DeepseekStreamToolCall,\n type TokenUsage as DeepseekTokenUsage,\n type ToolCall as DeepseekToolCall,\n} from './deepseek';\nexport {\n default as OpenAICompatible,\n type AssistantMessage,\n type ChatCompletionOptions,\n type Message,\n type MessageRole,\n type OpenAICompatibleConfig,\n type RawTool,\n type ResponseToolCall,\n type StreamChunk,\n type StreamToolCall,\n type TokenUsage,\n type ToolCall,\n} from './openai-compatible';\n","import { Component } from '@astack-tech/core';\nimport type { Port } from '@astack-tech/core';\nimport OpenAI from 'openai';\n\n/**\n * Options for a provider that speaks the OpenAI chat completions API.\n */\nexport interface OpenAICompatibleConfig {\n /** API key of the provider. */\n apiKey: string;\n /** Model name, for example `gpt-4o-mini`, `qwen-plus` or `llama3.1`. */\n model: string;\n /** API base URL. Defaults to `https://api.openai.com/v1`. */\n baseURL?: string | undefined;\n /** Provider name used in errors and logs. Defaults to `The model API`. */\n name?: string | undefined;\n /** Sampling temperature. Defaults to `0.7`. */\n temperature?: number | undefined;\n /** Maximum number of generated tokens. */\n maxTokens?: number | undefined;\n /** Nucleus sampling probability. */\n topP?: number | undefined;\n /** System prompt added before every request. */\n systemPrompt?: string | undefined;\n /**\n * Tools given as `{ name, description, parameters }` objects.\n * When this list is not empty, it takes precedence over `tools`.\n */\n rawTools?: RawTool[] | undefined;\n /** Tools in the OpenAI-compatible `{ type: 'function', function }` format. */\n tools?: unknown[] | undefined;\n /** Allows the OpenAI client to run in a browser. Defaults to `false`. */\n dangerouslyAllowBrowser?: boolean | undefined;\n /** Logs API errors to the console. Defaults to `false`. */\n verbose?: boolean | undefined;\n}\n\n/**\n * Tool description accepted by `rawTools` and `temporaryTools`.\n */\nexport interface RawTool {\n /** Unique tool name. */\n name: string;\n /** Description that tells the model when to use the tool. */\n description: string;\n /** JSON Schema of the arguments. Defaults to `{}`. */\n parameters?: Record<string, unknown> | undefined;\n}\n\n/**\n * Role of a chat message.\n */\nexport type MessageRole = 'system' | 'user' | 'assistant' | 'tool';\n\n/**\n * Tool call in the internal format.\n */\nexport interface ToolCall {\n /** Identifier assigned by the API. */\n id?: string | undefined;\n /** Name of the tool. */\n tool_name: string;\n /** Parsed arguments. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Tool call returned by `chatCompletion`. It carries both the OpenAI shape\n * (`type`, `function`) and the internal shape (`tool_name`, `arguments`).\n */\nexport interface ResponseToolCall {\n /** Identifier assigned by the API. A `tool` message answers the call with it. */\n id: string;\n /** Always `function`. */\n type: 'function';\n /** The call in the OpenAI shape: function name and raw JSON arguments. An empty argument string is sent as `{}`. */\n function: {\n /** Name of the tool to call. */\n name: string;\n /** Arguments as JSON text. */\n arguments: string;\n };\n /** Name of the tool, same as `function.name`. */\n tool_name: string;\n /** Parsed arguments. JSON that is not an object becomes `{}`. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Partial tool call emitted while streaming. `arguments` holds the raw JSON fragment.\n */\nexport interface StreamToolCall {\n /** Identifier assigned by the API. Only the first delta of a call carries it; later deltas have `''`. */\n id: string;\n /** Always `function`. */\n type: 'function';\n /** The delta in the OpenAI shape. `arguments` holds only this delta's JSON fragment. */\n function: {\n /** Name of the tool to call. */\n name: string;\n /** Arguments as JSON text. */\n arguments: string;\n };\n /** Name of the tool when this delta carries it, otherwise `''`. */\n tool_name: string;\n /** Raw JSON fragment of this delta. Concatenate the fragments of a call to get its arguments. */\n arguments: string;\n}\n\n/**\n * Token usage reported by the API.\n */\nexport interface TokenUsage {\n /** Tokens in the generated reply. */\n completion_tokens: number;\n /** Tokens in the request. */\n prompt_tokens: number;\n /** Prompt tokens served from the provider's context cache (reported by DeepSeek). */\n prompt_cache_hit_tokens?: number | undefined;\n /** Prompt tokens not served from the cache. */\n prompt_cache_miss_tokens?: number | undefined;\n /** Sum of prompt and completion tokens. */\n total_tokens: number;\n}\n\n/**\n * Chat message accepted as input. It matches the agent message contract in\n * `@astack-tech/components`, so agent histories can be passed directly.\n */\nexport interface Message {\n /** One of `system`, `user`, `assistant` or `tool` (see `MessageRole`). */\n role: string;\n /** Text content. Omitted content is sent as no content. */\n content?: string | undefined;\n /** Accepted for compatibility. Assistant tool calls are not forwarded to the API. */\n tool_calls?: readonly unknown[] | undefined;\n /** Identifier of the call that a `tool` message answers. */\n tool_call_id?: string | undefined;\n /** Ignored on input. Present so agent histories can be passed unchanged. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Assistant message returned by `chatCompletion`.\n */\nexport interface AssistantMessage {\n /** Always `assistant`. */\n role: 'assistant';\n /** Reply text. Empty when the model only calls tools. */\n content: string;\n /** Tool calls requested by the model, if any. */\n tool_calls?: ResponseToolCall[] | undefined;\n /** Token usage of this request, when the API reports it. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Chunk yielded by `streamChatCompletion`.\n */\nexport interface StreamChunk {\n /** Always `assistant`. */\n role: 'assistant';\n /** Text delta. Empty in chunks that carry tool calls or usage. */\n content: string;\n /** Tool-call deltas in this chunk. */\n tool_calls?: StreamToolCall[] | undefined;\n /** Token usage. Sent once, in the final chunk, when the API reports it. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Per-call options.\n */\nexport interface ChatCompletionOptions {\n /** Tools for this call only. They replace the configured tools. */\n temporaryTools?: unknown[] | undefined;\n}\n\ntype ChatMessageParam = OpenAI.Chat.ChatCompletionMessageParam;\ntype ChatTool = OpenAI.Chat.ChatCompletionTool;\n\ninterface ApiUsage {\n completion_tokens?: number;\n prompt_tokens?: number;\n total_tokens?: number;\n prompt_cache_hit_tokens?: number;\n prompt_cache_miss_tokens?: number;\n}\n\nconst DEFAULT_BASE_URL = 'https://api.openai.com/v1';\nconst DEFAULT_NAME = 'The model API';\nconst DEFAULT_TEMPERATURE = 0.7;\nconst FALLBACK_TEXT = 'API call failed';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isOptionalString(value: unknown): value is string | undefined {\n return value === undefined || typeof value === 'string';\n}\n\nfunction isCount(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value);\n}\n\nfunction isTokenUsage(value: unknown): value is TokenUsage {\n return (\n isRecord(value) &&\n isCount(value['completion_tokens']) &&\n isCount(value['prompt_tokens']) &&\n isCount(value['total_tokens']) &&\n (value['prompt_cache_hit_tokens'] === undefined || isCount(value['prompt_cache_hit_tokens'])) &&\n (value['prompt_cache_miss_tokens'] === undefined || isCount(value['prompt_cache_miss_tokens']))\n );\n}\n\n/** Whether `value` is a `Message`, checked field by field. The role is checked when the message is sent. */\nfunction isMessage(value: unknown): value is Message {\n return (\n isRecord(value) &&\n typeof value['role'] === 'string' &&\n isOptionalString(value['content']) &&\n (value['tool_calls'] === undefined || Array.isArray(value['tool_calls'])) &&\n isOptionalString(value['tool_call_id']) &&\n (value['usage'] === undefined || isTokenUsage(value['usage']))\n );\n}\n\nfunction isMessageList(value: unknown): value is Message[] {\n return Array.isArray(value) && value.every(item => isMessage(item));\n}\n\n/**\n * Converts a `{ name, description, parameters }` object to the OpenAI tool format.\n * Objects already in the OpenAI format are returned unchanged.\n */\nfunction toApiTool(tool: unknown): ChatTool {\n const raw = isRecord(tool) ? tool : {};\n const definition = raw['function'];\n if (raw['type'] === 'function' && isRecord(definition)) {\n const { name, description, parameters, strict } = definition;\n return {\n type: 'function',\n function: {\n name: typeof name === 'string' ? name : '',\n ...(typeof description === 'string' ? { description } : {}),\n ...(isRecord(parameters) ? { parameters } : {}),\n ...(typeof strict === 'boolean' ? { strict } : {}),\n },\n };\n }\n const { name, description, parameters } = raw;\n return {\n type: 'function',\n function: {\n name: typeof name === 'string' ? name : '',\n description: typeof description === 'string' ? description : '',\n parameters: isRecord(parameters) ? parameters : {},\n },\n };\n}\n\n/**\n * Parses tool-call arguments. Anything that is not a JSON object becomes `{}`.\n */\nfunction parseArguments(raw: string): Record<string, unknown> {\n if (raw.trim().length === 0) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n return isRecord(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\nfunction mapUsage(usage: ApiUsage): TokenUsage {\n return {\n completion_tokens: usage.completion_tokens ?? 0,\n prompt_tokens: usage.prompt_tokens ?? 0,\n total_tokens: usage.total_tokens ?? 0,\n ...(usage.prompt_cache_hit_tokens === undefined\n ? {}\n : { prompt_cache_hit_tokens: usage.prompt_cache_hit_tokens }),\n ...(usage.prompt_cache_miss_tokens === undefined\n ? {}\n : { prompt_cache_miss_tokens: usage.prompt_cache_miss_tokens }),\n };\n}\n\n/**\n * Model provider for any API that speaks the OpenAI chat completions protocol:\n * OpenAI itself, and the compatible endpoints of DeepSeek, Qwen, Moonshot, Ollama,\n * vLLM and others. Set `baseURL` and `model` for the provider you use.\n *\n * Ports:\n * - `prompt` (in): a text prompt. The answer is sent on `completion`.\n * - `messages` (in): a list of chat messages. The reply is sent on `message`.\n */\nclass OpenAICompatible extends Component {\n private readonly client: OpenAI;\n private readonly model: string;\n private readonly temperature: number;\n private readonly maxTokens: number | undefined;\n private readonly topP: number | undefined;\n private readonly systemPrompt: string | undefined;\n private readonly tools: unknown[] | undefined;\n private readonly verbose: boolean;\n private readonly name: string;\n\n constructor(config: OpenAICompatibleConfig) {\n super({});\n\n this.client = new OpenAI({\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n dangerouslyAllowBrowser: config.dangerouslyAllowBrowser ?? false,\n });\n\n this.model = config.model;\n this.name = config.name ?? DEFAULT_NAME;\n this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;\n this.maxTokens = config.maxTokens;\n this.topP = config.topP;\n this.systemPrompt = config.systemPrompt;\n this.verbose = config.verbose ?? false;\n this.tools =\n config.rawTools !== undefined && config.rawTools.length > 0\n ? config.rawTools.map(toApiTool)\n : config.tools;\n\n Component.Port.I('prompt').attach(this);\n Component.Port.I('messages').attach(this);\n Component.Port.O('completion').attach(this);\n Component.Port.O('message').attach(this);\n }\n\n /**\n * Generates a completion for a single prompt.\n *\n * @returns The text answer, or the JSON-encoded assistant message when it contains tool calls.\n */\n async generateCompletion(prompt: string): Promise<string> {\n const response = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: [...this.systemMessages(), { role: 'user', content: prompt }],\n ...this.toolsParam(this.tools),\n });\n\n const message = this.firstMessage(response);\n if (message.tool_calls !== undefined && message.tool_calls.length > 0) {\n return JSON.stringify(message);\n }\n return message.content ?? '';\n }\n\n /**\n * Sends a conversation and returns the assistant reply.\n */\n async chatCompletion(\n messages: Message[],\n options?: ChatCompletionOptions\n ): Promise<AssistantMessage> {\n const response = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: this.formatMessages(messages),\n ...this.toolsParam(options?.temporaryTools ?? this.tools),\n });\n\n const message = this.firstMessage(response);\n const result: AssistantMessage = { role: 'assistant', content: message.content ?? '' };\n\n if (response.usage !== undefined) {\n result.usage = mapUsage(response.usage);\n }\n\n if (message.tool_calls !== undefined && message.tool_calls.length > 0) {\n result.tool_calls = message.tool_calls.map(toolCall => {\n if (toolCall.type !== 'function') {\n throw new Error(`Unsupported tool call type: ${toolCall.type}`);\n }\n const rawArguments = toolCall.function.arguments;\n return {\n id: toolCall.id,\n type: 'function',\n function: {\n name: toolCall.function.name,\n arguments: rawArguments.length > 0 ? rawArguments : '{}',\n },\n tool_name: toolCall.function.name,\n arguments: parseArguments(rawArguments),\n };\n });\n }\n\n return result;\n }\n\n /**\n * Streams the assistant reply. Each chunk holds a text delta, tool-call deltas, or usage.\n */\n async *streamChatCompletion(\n messages: Message[],\n options?: ChatCompletionOptions\n ): AsyncGenerator<StreamChunk> {\n const stream = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: this.formatMessages(messages),\n ...this.toolsParam(options?.temporaryTools ?? this.tools),\n stream: true,\n });\n\n for await (const chunk of stream) {\n const delta = chunk.choices[0]?.delta;\n\n const text = delta?.content ?? '';\n if (text !== '') {\n yield { role: 'assistant', content: text };\n }\n\n if (delta?.tool_calls) {\n yield {\n role: 'assistant',\n content: '',\n tool_calls: delta.tool_calls.map(toolCall => {\n const name = toolCall.function?.name ?? '';\n const args = toolCall.function?.arguments ?? '';\n return {\n id: toolCall.id ?? '',\n type: 'function',\n function: { name, arguments: args },\n tool_name: name,\n arguments: args,\n };\n }),\n };\n }\n\n if (chunk.usage) {\n yield { role: 'assistant', content: '', usage: mapUsage(chunk.usage) };\n }\n }\n }\n\n /**\n * Runs the model outside a pipeline.\n *\n * @param input - A prompt string or a list of messages.\n */\n override run(input: string | Message[]): Promise<string | AssistantMessage> {\n return typeof input === 'string' ? this.generateCompletion(input) : this.chatCompletion(input);\n }\n\n /**\n * Connects the `prompt` and `messages` input ports to the model.\n * API errors produce a fallback value so the pipeline keeps flowing. Input of the wrong\n * type fails the pipeline run with a `TypeError` before the API is called.\n */\n override _transform($i: Port, $o: Port): void {\n $i('prompt').receive(prompt => {\n if (typeof prompt !== 'string') {\n throw new TypeError(`${this.name}: the prompt port expects a string.`);\n }\n return this.generateCompletion(prompt).then(\n completion => {\n $o('completion').send(completion);\n },\n (error: unknown) => {\n this.logError(error);\n $o('completion').send(FALLBACK_TEXT);\n }\n );\n });\n\n $i('messages').receive(messages => {\n if (!isMessageList(messages)) {\n throw new TypeError(`${this.name}: the messages port expects an array of messages.`);\n }\n return this.chatCompletion(messages).then(\n message => {\n $o('message').send(message);\n },\n (error: unknown) => {\n this.logError(error);\n $o('message').send({ role: 'assistant', content: FALLBACK_TEXT });\n }\n );\n });\n }\n\n private baseRequest(): {\n model: string;\n temperature: number;\n max_tokens?: number;\n top_p?: number;\n } {\n return {\n model: this.model,\n temperature: this.temperature,\n ...(this.maxTokens === undefined ? {} : { max_tokens: this.maxTokens }),\n ...(this.topP === undefined ? {} : { top_p: this.topP }),\n };\n }\n\n private toolsParam(tools: unknown[] | undefined): { tools?: ChatTool[] } {\n return tools !== undefined && tools.length > 0 ? { tools: tools.map(toApiTool) } : {};\n }\n\n private systemMessages(): ChatMessageParam[] {\n return this.systemPrompt === undefined || this.systemPrompt === ''\n ? []\n : [{ role: 'system', content: this.systemPrompt }];\n }\n\n /**\n * Converts input messages to API messages. Assistant `tool_calls` are not forwarded:\n * agents send tool results back as user messages, and the API rejects assistant tool\n * calls that are not followed by tool messages.\n */\n private formatMessages(messages: Message[]): ChatMessageParam[] {\n return [...this.systemMessages(), ...messages.map(message => this.toApiMessage(message))];\n }\n\n /**\n * Converts one input message to its API shape.\n *\n * @throws Error when the role is not one of `MessageRole`, or a `tool` message has no\n * `tool_call_id`. The API would reject both.\n */\n private toApiMessage(message: Message): ChatMessageParam {\n const content = message.content ?? '';\n switch (message.role) {\n case 'system':\n return { role: 'system', content };\n case 'user':\n return { role: 'user', content };\n case 'assistant':\n return message.content === undefined\n ? { role: 'assistant' }\n : { role: 'assistant', content: message.content };\n case 'tool':\n if (message.tool_call_id === undefined || message.tool_call_id === '') {\n throw new Error(\n `${this.name}: a tool message needs the tool_call_id of the call it answers.`\n );\n }\n return { role: 'tool', content, tool_call_id: message.tool_call_id };\n default:\n throw new Error(\n `${this.name}: unsupported message role '${message.role}'. Use system, user, assistant or tool.`\n );\n }\n }\n\n private firstMessage(response: OpenAI.Chat.ChatCompletion): OpenAI.Chat.ChatCompletionMessage {\n const choice = response.choices[0];\n if (choice === undefined) {\n throw new Error(`${this.name} returned no choices.`);\n }\n return choice.message;\n }\n\n private logError(error: unknown): void {\n if (this.verbose) {\n // eslint-disable-next-line no-console -- opt-in diagnostics controlled by `verbose`.\n console.error(`[${this.name}] API call failed:`, error);\n }\n }\n}\n\nexport default OpenAICompatible;\n","import OpenAICompatible from '../openai-compatible';\nimport type { OpenAICompatibleConfig } from '../openai-compatible';\n\nexport type {\n AssistantMessage,\n ChatCompletionOptions,\n Message,\n MessageRole,\n RawTool,\n ResponseToolCall,\n StreamChunk,\n StreamToolCall,\n TokenUsage,\n ToolCall,\n} from '../openai-compatible';\n\nconst DEFAULT_BASE_URL = 'https://api.deepseek.com/v1';\nconst DEFAULT_MODEL = 'deepseek-chat';\n\n/**\n * Options for the DeepSeek model provider. They are the options of\n * `OpenAICompatible`, with DeepSeek's endpoint and model as defaults.\n */\nexport interface DeepseekConfig extends Omit<OpenAICompatibleConfig, 'model' | 'name'> {\n /** Model name. Defaults to `deepseek-chat`. */\n model?: string | undefined;\n /** API base URL. Defaults to `https://api.deepseek.com/v1`. */\n baseURL?: string | undefined;\n}\n\n/**\n * DeepSeek model provider: `OpenAICompatible` set up for the DeepSeek API.\n *\n * Ports:\n * - `prompt` (in): a text prompt. The answer is sent on `completion`.\n * - `messages` (in): a list of chat messages. The reply is sent on `message`.\n */\nclass Deepseek extends OpenAICompatible {\n constructor(config: DeepseekConfig) {\n super({\n ...config,\n model: config.model ?? DEFAULT_MODEL,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n name: 'DeepSeek',\n });\n }\n}\n\nexport default Deepseek;\n"],"mappings":"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","names":["name","description","parameters","DEFAULT_BASE_URL"]}
@@ -45,6 +45,21 @@ var FALLBACK_TEXT = "API call failed";
45
45
  function isRecord(value) {
46
46
  return typeof value === "object" && value !== null && !Array.isArray(value);
47
47
  }
48
+ function isOptionalString(value) {
49
+ return value === void 0 || typeof value === "string";
50
+ }
51
+ function isCount(value) {
52
+ return typeof value === "number" && Number.isFinite(value);
53
+ }
54
+ function isTokenUsage(value) {
55
+ return isRecord(value) && isCount(value["completion_tokens"]) && isCount(value["prompt_tokens"]) && isCount(value["total_tokens"]) && (value["prompt_cache_hit_tokens"] === void 0 || isCount(value["prompt_cache_hit_tokens"])) && (value["prompt_cache_miss_tokens"] === void 0 || isCount(value["prompt_cache_miss_tokens"]));
56
+ }
57
+ function isMessage(value) {
58
+ return isRecord(value) && typeof value["role"] === "string" && isOptionalString(value["content"]) && (value["tool_calls"] === void 0 || Array.isArray(value["tool_calls"])) && isOptionalString(value["tool_call_id"]) && (value["usage"] === void 0 || isTokenUsage(value["usage"]));
59
+ }
60
+ function isMessageList(value) {
61
+ return Array.isArray(value) && value.every((item) => isMessage(item));
62
+ }
48
63
  function toApiTool(tool) {
49
64
  const raw = isRecord(tool) ? tool : {};
50
65
  const definition = raw["function"];
@@ -219,11 +234,15 @@ var OpenAICompatible = class extends import_core.Component {
219
234
  }
220
235
  /**
221
236
  * Connects the `prompt` and `messages` input ports to the model.
222
- * API errors produce a fallback value so the pipeline keeps flowing.
237
+ * API errors produce a fallback value so the pipeline keeps flowing. Input of the wrong
238
+ * type fails the pipeline run with a `TypeError` before the API is called.
223
239
  */
224
240
  _transform($i, $o) {
225
241
  $i("prompt").receive((prompt) => {
226
- this.generateCompletion(prompt).then(
242
+ if (typeof prompt !== "string") {
243
+ throw new TypeError(`${this.name}: the prompt port expects a string.`);
244
+ }
245
+ return this.generateCompletion(prompt).then(
227
246
  (completion) => {
228
247
  $o("completion").send(completion);
229
248
  },
@@ -234,7 +253,10 @@ var OpenAICompatible = class extends import_core.Component {
234
253
  );
235
254
  });
236
255
  $i("messages").receive((messages) => {
237
- this.chatCompletion(messages).then(
256
+ if (!isMessageList(messages)) {
257
+ throw new TypeError(`${this.name}: the messages port expects an array of messages.`);
258
+ }
259
+ return this.chatCompletion(messages).then(
238
260
  (message) => {
239
261
  $o("message").send(message);
240
262
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/model-provider/index.ts","../../src/model-provider/openai-compatible/index.ts","../../src/model-provider/deepseek/index.ts"],"sourcesContent":["export {\n default as Deepseek,\n type AssistantMessage as DeepseekAssistantMessage,\n type ChatCompletionOptions as DeepseekChatCompletionOptions,\n type DeepseekConfig,\n type Message as DeepseekMessage,\n type MessageRole as DeepseekMessageRole,\n type RawTool as DeepseekRawTool,\n type ResponseToolCall as DeepseekResponseToolCall,\n type StreamChunk as DeepseekStreamChunk,\n type StreamToolCall as DeepseekStreamToolCall,\n type TokenUsage as DeepseekTokenUsage,\n type ToolCall as DeepseekToolCall,\n} from './deepseek';\nexport {\n default as OpenAICompatible,\n type AssistantMessage,\n type ChatCompletionOptions,\n type Message,\n type MessageRole,\n type OpenAICompatibleConfig,\n type RawTool,\n type ResponseToolCall,\n type StreamChunk,\n type StreamToolCall,\n type TokenUsage,\n type ToolCall,\n} from './openai-compatible';\n","import { Component } from '@astack-tech/core';\nimport type { Port } from '@astack-tech/core';\nimport OpenAI from 'openai';\n\n/**\n * Options for a provider that speaks the OpenAI chat completions API.\n */\nexport interface OpenAICompatibleConfig {\n /** API key of the provider. */\n apiKey: string;\n /** Model name, for example `gpt-4o-mini`, `qwen-plus` or `llama3.1`. */\n model: string;\n /** API base URL. Defaults to `https://api.openai.com/v1`. */\n baseURL?: string | undefined;\n /** Provider name used in errors and logs. Defaults to `The model API`. */\n name?: string | undefined;\n /** Sampling temperature. Defaults to `0.7`. */\n temperature?: number | undefined;\n /** Maximum number of generated tokens. */\n maxTokens?: number | undefined;\n /** Nucleus sampling probability. */\n topP?: number | undefined;\n /** System prompt added before every request. */\n systemPrompt?: string | undefined;\n /**\n * Tools given as `{ name, description, parameters }` objects.\n * When this list is not empty, it takes precedence over `tools`.\n */\n rawTools?: RawTool[] | undefined;\n /** Tools in the OpenAI-compatible `{ type: 'function', function }` format. */\n tools?: unknown[] | undefined;\n /** Allows the OpenAI client to run in a browser. Defaults to `false`. */\n dangerouslyAllowBrowser?: boolean | undefined;\n /** Logs API errors to the console. Defaults to `false`. */\n verbose?: boolean | undefined;\n}\n\n/**\n * Tool description accepted by `rawTools` and `temporaryTools`.\n */\nexport interface RawTool {\n /** Unique tool name. */\n name: string;\n /** Description that tells the model when to use the tool. */\n description: string;\n /** JSON Schema of the arguments. Defaults to `{}`. */\n parameters?: Record<string, unknown> | undefined;\n}\n\n/**\n * Role of a chat message.\n */\nexport type MessageRole = 'system' | 'user' | 'assistant' | 'tool';\n\n/**\n * Tool call in the internal format.\n */\nexport interface ToolCall {\n /** Identifier assigned by the API. */\n id?: string | undefined;\n /** Name of the tool. */\n tool_name: string;\n /** Parsed arguments. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Tool call returned by `chatCompletion`. It carries both the OpenAI shape\n * (`type`, `function`) and the internal shape (`tool_name`, `arguments`).\n */\nexport interface ResponseToolCall {\n /** Identifier assigned by the API. A `tool` message answers the call with it. */\n id: string;\n /** Always `function`. */\n type: 'function';\n /** The call in the OpenAI shape: function name and raw JSON arguments. An empty argument string is sent as `{}`. */\n function: {\n /** Name of the tool to call. */\n name: string;\n /** Arguments as JSON text. */\n arguments: string;\n };\n /** Name of the tool, same as `function.name`. */\n tool_name: string;\n /** Parsed arguments. JSON that is not an object becomes `{}`. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Partial tool call emitted while streaming. `arguments` holds the raw JSON fragment.\n */\nexport interface StreamToolCall {\n /** Identifier assigned by the API. Only the first delta of a call carries it; later deltas have `''`. */\n id: string;\n /** Always `function`. */\n type: 'function';\n /** The delta in the OpenAI shape. `arguments` holds only this delta's JSON fragment. */\n function: {\n /** Name of the tool to call. */\n name: string;\n /** Arguments as JSON text. */\n arguments: string;\n };\n /** Name of the tool when this delta carries it, otherwise `''`. */\n tool_name: string;\n /** Raw JSON fragment of this delta. Concatenate the fragments of a call to get its arguments. */\n arguments: string;\n}\n\n/**\n * Token usage reported by the API.\n */\nexport interface TokenUsage {\n /** Tokens in the generated reply. */\n completion_tokens: number;\n /** Tokens in the request. */\n prompt_tokens: number;\n /** Prompt tokens served from the provider's context cache (reported by DeepSeek). */\n prompt_cache_hit_tokens?: number | undefined;\n /** Prompt tokens not served from the cache. */\n prompt_cache_miss_tokens?: number | undefined;\n /** Sum of prompt and completion tokens. */\n total_tokens: number;\n}\n\n/**\n * Chat message accepted as input. It matches the agent message contract in\n * `@astack-tech/components`, so agent histories can be passed directly.\n */\nexport interface Message {\n /** One of `system`, `user`, `assistant` or `tool` (see `MessageRole`). */\n role: string;\n /** Text content. Omitted content is sent as no content. */\n content?: string | undefined;\n /** Accepted for compatibility. Assistant tool calls are not forwarded to the API. */\n tool_calls?: readonly unknown[] | undefined;\n /** Identifier of the call that a `tool` message answers. */\n tool_call_id?: string | undefined;\n /** Ignored on input. Present so agent histories can be passed unchanged. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Assistant message returned by `chatCompletion`.\n */\nexport interface AssistantMessage {\n /** Always `assistant`. */\n role: 'assistant';\n /** Reply text. Empty when the model only calls tools. */\n content: string;\n /** Tool calls requested by the model, if any. */\n tool_calls?: ResponseToolCall[] | undefined;\n /** Token usage of this request, when the API reports it. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Chunk yielded by `streamChatCompletion`.\n */\nexport interface StreamChunk {\n /** Always `assistant`. */\n role: 'assistant';\n /** Text delta. Empty in chunks that carry tool calls or usage. */\n content: string;\n /** Tool-call deltas in this chunk. */\n tool_calls?: StreamToolCall[] | undefined;\n /** Token usage. Sent once, in the final chunk, when the API reports it. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Per-call options.\n */\nexport interface ChatCompletionOptions {\n /** Tools for this call only. They replace the configured tools. */\n temporaryTools?: unknown[] | undefined;\n}\n\ntype ChatMessageParam = OpenAI.Chat.ChatCompletionMessageParam;\ntype ChatTool = OpenAI.Chat.ChatCompletionTool;\n\ninterface ApiUsage {\n completion_tokens?: number;\n prompt_tokens?: number;\n total_tokens?: number;\n prompt_cache_hit_tokens?: number;\n prompt_cache_miss_tokens?: number;\n}\n\nconst DEFAULT_BASE_URL = 'https://api.openai.com/v1';\nconst DEFAULT_NAME = 'The model API';\nconst DEFAULT_TEMPERATURE = 0.7;\nconst FALLBACK_TEXT = 'API call failed';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Converts a `{ name, description, parameters }` object to the OpenAI tool format.\n * Objects already in the OpenAI format are returned unchanged.\n */\nfunction toApiTool(tool: unknown): ChatTool {\n const raw = isRecord(tool) ? tool : {};\n const definition = raw['function'];\n if (raw['type'] === 'function' && isRecord(definition)) {\n const { name, description, parameters, strict } = definition;\n return {\n type: 'function',\n function: {\n name: typeof name === 'string' ? name : '',\n ...(typeof description === 'string' ? { description } : {}),\n ...(isRecord(parameters) ? { parameters } : {}),\n ...(typeof strict === 'boolean' ? { strict } : {}),\n },\n };\n }\n const { name, description, parameters } = raw;\n return {\n type: 'function',\n function: {\n name: typeof name === 'string' ? name : '',\n description: typeof description === 'string' ? description : '',\n parameters: isRecord(parameters) ? parameters : {},\n },\n };\n}\n\n/**\n * Parses tool-call arguments. Anything that is not a JSON object becomes `{}`.\n */\nfunction parseArguments(raw: string): Record<string, unknown> {\n if (raw.trim().length === 0) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n return isRecord(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\nfunction mapUsage(usage: ApiUsage): TokenUsage {\n return {\n completion_tokens: usage.completion_tokens ?? 0,\n prompt_tokens: usage.prompt_tokens ?? 0,\n total_tokens: usage.total_tokens ?? 0,\n ...(usage.prompt_cache_hit_tokens === undefined\n ? {}\n : { prompt_cache_hit_tokens: usage.prompt_cache_hit_tokens }),\n ...(usage.prompt_cache_miss_tokens === undefined\n ? {}\n : { prompt_cache_miss_tokens: usage.prompt_cache_miss_tokens }),\n };\n}\n\n/**\n * Model provider for any API that speaks the OpenAI chat completions protocol:\n * OpenAI itself, and the compatible endpoints of DeepSeek, Qwen, Moonshot, Ollama,\n * vLLM and others. Set `baseURL` and `model` for the provider you use.\n *\n * Ports:\n * - `prompt` (in): a text prompt. The answer is sent on `completion`.\n * - `messages` (in): a list of chat messages. The reply is sent on `message`.\n */\nclass OpenAICompatible extends Component {\n private readonly client: OpenAI;\n private readonly model: string;\n private readonly temperature: number;\n private readonly maxTokens: number | undefined;\n private readonly topP: number | undefined;\n private readonly systemPrompt: string | undefined;\n private readonly tools: unknown[] | undefined;\n private readonly verbose: boolean;\n private readonly name: string;\n\n constructor(config: OpenAICompatibleConfig) {\n super({});\n\n this.client = new OpenAI({\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n dangerouslyAllowBrowser: config.dangerouslyAllowBrowser ?? false,\n });\n\n this.model = config.model;\n this.name = config.name ?? DEFAULT_NAME;\n this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;\n this.maxTokens = config.maxTokens;\n this.topP = config.topP;\n this.systemPrompt = config.systemPrompt;\n this.verbose = config.verbose ?? false;\n this.tools =\n config.rawTools !== undefined && config.rawTools.length > 0\n ? config.rawTools.map(toApiTool)\n : config.tools;\n\n Component.Port.I('prompt').attach(this);\n Component.Port.I('messages').attach(this);\n Component.Port.O('completion').attach(this);\n Component.Port.O('message').attach(this);\n }\n\n /**\n * Generates a completion for a single prompt.\n *\n * @returns The text answer, or the JSON-encoded assistant message when it contains tool calls.\n */\n async generateCompletion(prompt: string): Promise<string> {\n const response = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: [...this.systemMessages(), { role: 'user', content: prompt }],\n ...this.toolsParam(this.tools),\n });\n\n const message = this.firstMessage(response);\n if (message.tool_calls !== undefined && message.tool_calls.length > 0) {\n return JSON.stringify(message);\n }\n return message.content ?? '';\n }\n\n /**\n * Sends a conversation and returns the assistant reply.\n */\n async chatCompletion(\n messages: Message[],\n options?: ChatCompletionOptions\n ): Promise<AssistantMessage> {\n const response = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: this.formatMessages(messages),\n ...this.toolsParam(options?.temporaryTools ?? this.tools),\n });\n\n const message = this.firstMessage(response);\n const result: AssistantMessage = { role: 'assistant', content: message.content ?? '' };\n\n if (response.usage !== undefined) {\n result.usage = mapUsage(response.usage);\n }\n\n if (message.tool_calls !== undefined && message.tool_calls.length > 0) {\n result.tool_calls = message.tool_calls.map(toolCall => {\n if (toolCall.type !== 'function') {\n throw new Error(`Unsupported tool call type: ${toolCall.type}`);\n }\n const rawArguments = toolCall.function.arguments;\n return {\n id: toolCall.id,\n type: 'function',\n function: {\n name: toolCall.function.name,\n arguments: rawArguments.length > 0 ? rawArguments : '{}',\n },\n tool_name: toolCall.function.name,\n arguments: parseArguments(rawArguments),\n };\n });\n }\n\n return result;\n }\n\n /**\n * Streams the assistant reply. Each chunk holds a text delta, tool-call deltas, or usage.\n */\n async *streamChatCompletion(\n messages: Message[],\n options?: ChatCompletionOptions\n ): AsyncGenerator<StreamChunk> {\n const stream = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: this.formatMessages(messages),\n ...this.toolsParam(options?.temporaryTools ?? this.tools),\n stream: true,\n });\n\n for await (const chunk of stream) {\n const delta = chunk.choices[0]?.delta;\n\n const text = delta?.content ?? '';\n if (text !== '') {\n yield { role: 'assistant', content: text };\n }\n\n if (delta?.tool_calls) {\n yield {\n role: 'assistant',\n content: '',\n tool_calls: delta.tool_calls.map(toolCall => {\n const name = toolCall.function?.name ?? '';\n const args = toolCall.function?.arguments ?? '';\n return {\n id: toolCall.id ?? '',\n type: 'function',\n function: { name, arguments: args },\n tool_name: name,\n arguments: args,\n };\n }),\n };\n }\n\n if (chunk.usage) {\n yield { role: 'assistant', content: '', usage: mapUsage(chunk.usage) };\n }\n }\n }\n\n /**\n * Runs the model outside a pipeline.\n *\n * @param input - A prompt string or a list of messages.\n */\n override run(input: string | Message[]): Promise<string | AssistantMessage> {\n return typeof input === 'string' ? this.generateCompletion(input) : this.chatCompletion(input);\n }\n\n /**\n * Connects the `prompt` and `messages` input ports to the model.\n * API errors produce a fallback value so the pipeline keeps flowing.\n */\n override _transform($i: Port, $o: Port): void {\n $i('prompt').receive((prompt: string) => {\n this.generateCompletion(prompt).then(\n completion => {\n $o('completion').send(completion);\n },\n (error: unknown) => {\n this.logError(error);\n $o('completion').send(FALLBACK_TEXT);\n }\n );\n });\n\n $i('messages').receive((messages: Message[]) => {\n this.chatCompletion(messages).then(\n message => {\n $o('message').send(message);\n },\n (error: unknown) => {\n this.logError(error);\n $o('message').send({ role: 'assistant', content: FALLBACK_TEXT });\n }\n );\n });\n }\n\n private baseRequest(): {\n model: string;\n temperature: number;\n max_tokens?: number;\n top_p?: number;\n } {\n return {\n model: this.model,\n temperature: this.temperature,\n ...(this.maxTokens === undefined ? {} : { max_tokens: this.maxTokens }),\n ...(this.topP === undefined ? {} : { top_p: this.topP }),\n };\n }\n\n private toolsParam(tools: unknown[] | undefined): { tools?: ChatTool[] } {\n return tools !== undefined && tools.length > 0 ? { tools: tools.map(toApiTool) } : {};\n }\n\n private systemMessages(): ChatMessageParam[] {\n return this.systemPrompt === undefined || this.systemPrompt === ''\n ? []\n : [{ role: 'system', content: this.systemPrompt }];\n }\n\n /**\n * Converts input messages to API messages. Assistant `tool_calls` are not forwarded:\n * agents send tool results back as user messages, and the API rejects assistant tool\n * calls that are not followed by tool messages.\n */\n private formatMessages(messages: Message[]): ChatMessageParam[] {\n return [...this.systemMessages(), ...messages.map(message => this.toApiMessage(message))];\n }\n\n /**\n * Converts one input message to its API shape.\n *\n * @throws Error when the role is not one of `MessageRole`, or a `tool` message has no\n * `tool_call_id`. The API would reject both.\n */\n private toApiMessage(message: Message): ChatMessageParam {\n const content = message.content ?? '';\n switch (message.role) {\n case 'system':\n return { role: 'system', content };\n case 'user':\n return { role: 'user', content };\n case 'assistant':\n return message.content === undefined\n ? { role: 'assistant' }\n : { role: 'assistant', content: message.content };\n case 'tool':\n if (message.tool_call_id === undefined || message.tool_call_id === '') {\n throw new Error(\n `${this.name}: a tool message needs the tool_call_id of the call it answers.`\n );\n }\n return { role: 'tool', content, tool_call_id: message.tool_call_id };\n default:\n throw new Error(\n `${this.name}: unsupported message role '${message.role}'. Use system, user, assistant or tool.`\n );\n }\n }\n\n private firstMessage(response: OpenAI.Chat.ChatCompletion): OpenAI.Chat.ChatCompletionMessage {\n const choice = response.choices[0];\n if (choice === undefined) {\n throw new Error(`${this.name} returned no choices.`);\n }\n return choice.message;\n }\n\n private logError(error: unknown): void {\n if (this.verbose) {\n // eslint-disable-next-line no-console -- opt-in diagnostics controlled by `verbose`.\n console.error(`[${this.name}] API call failed:`, error);\n }\n }\n}\n\nexport default OpenAICompatible;\n","import OpenAICompatible from '../openai-compatible';\nimport type { OpenAICompatibleConfig } from '../openai-compatible';\n\nexport type {\n AssistantMessage,\n ChatCompletionOptions,\n Message,\n MessageRole,\n RawTool,\n ResponseToolCall,\n StreamChunk,\n StreamToolCall,\n TokenUsage,\n ToolCall,\n} from '../openai-compatible';\n\nconst DEFAULT_BASE_URL = 'https://api.deepseek.com/v1';\nconst DEFAULT_MODEL = 'deepseek-chat';\n\n/**\n * Options for the DeepSeek model provider. They are the options of\n * `OpenAICompatible`, with DeepSeek's endpoint and model as defaults.\n */\nexport interface DeepseekConfig extends Omit<OpenAICompatibleConfig, 'model' | 'name'> {\n /** Model name. Defaults to `deepseek-chat`. */\n model?: string | undefined;\n /** API base URL. Defaults to `https://api.deepseek.com/v1`. */\n baseURL?: string | undefined;\n}\n\n/**\n * DeepSeek model provider: `OpenAICompatible` set up for the DeepSeek API.\n *\n * Ports:\n * - `prompt` (in): a text prompt. The answer is sent on `completion`.\n * - `messages` (in): a list of chat messages. The reply is sent on `message`.\n */\nclass Deepseek extends OpenAICompatible {\n constructor(config: DeepseekConfig) {\n super({\n ...config,\n model: config.model ?? DEFAULT_MODEL,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n name: 'DeepSeek',\n });\n }\n}\n\nexport default Deepseek;\n"],"mappings":"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","names":["name","description","parameters","OpenAI","DEFAULT_BASE_URL"]}
1
+ {"version":3,"sources":["../../src/model-provider/index.ts","../../src/model-provider/openai-compatible/index.ts","../../src/model-provider/deepseek/index.ts"],"sourcesContent":["export {\n default as Deepseek,\n type AssistantMessage as DeepseekAssistantMessage,\n type ChatCompletionOptions as DeepseekChatCompletionOptions,\n type DeepseekConfig,\n type Message as DeepseekMessage,\n type MessageRole as DeepseekMessageRole,\n type RawTool as DeepseekRawTool,\n type ResponseToolCall as DeepseekResponseToolCall,\n type StreamChunk as DeepseekStreamChunk,\n type StreamToolCall as DeepseekStreamToolCall,\n type TokenUsage as DeepseekTokenUsage,\n type ToolCall as DeepseekToolCall,\n} from './deepseek';\nexport {\n default as OpenAICompatible,\n type AssistantMessage,\n type ChatCompletionOptions,\n type Message,\n type MessageRole,\n type OpenAICompatibleConfig,\n type RawTool,\n type ResponseToolCall,\n type StreamChunk,\n type StreamToolCall,\n type TokenUsage,\n type ToolCall,\n} from './openai-compatible';\n","import { Component } from '@astack-tech/core';\nimport type { Port } from '@astack-tech/core';\nimport OpenAI from 'openai';\n\n/**\n * Options for a provider that speaks the OpenAI chat completions API.\n */\nexport interface OpenAICompatibleConfig {\n /** API key of the provider. */\n apiKey: string;\n /** Model name, for example `gpt-4o-mini`, `qwen-plus` or `llama3.1`. */\n model: string;\n /** API base URL. Defaults to `https://api.openai.com/v1`. */\n baseURL?: string | undefined;\n /** Provider name used in errors and logs. Defaults to `The model API`. */\n name?: string | undefined;\n /** Sampling temperature. Defaults to `0.7`. */\n temperature?: number | undefined;\n /** Maximum number of generated tokens. */\n maxTokens?: number | undefined;\n /** Nucleus sampling probability. */\n topP?: number | undefined;\n /** System prompt added before every request. */\n systemPrompt?: string | undefined;\n /**\n * Tools given as `{ name, description, parameters }` objects.\n * When this list is not empty, it takes precedence over `tools`.\n */\n rawTools?: RawTool[] | undefined;\n /** Tools in the OpenAI-compatible `{ type: 'function', function }` format. */\n tools?: unknown[] | undefined;\n /** Allows the OpenAI client to run in a browser. Defaults to `false`. */\n dangerouslyAllowBrowser?: boolean | undefined;\n /** Logs API errors to the console. Defaults to `false`. */\n verbose?: boolean | undefined;\n}\n\n/**\n * Tool description accepted by `rawTools` and `temporaryTools`.\n */\nexport interface RawTool {\n /** Unique tool name. */\n name: string;\n /** Description that tells the model when to use the tool. */\n description: string;\n /** JSON Schema of the arguments. Defaults to `{}`. */\n parameters?: Record<string, unknown> | undefined;\n}\n\n/**\n * Role of a chat message.\n */\nexport type MessageRole = 'system' | 'user' | 'assistant' | 'tool';\n\n/**\n * Tool call in the internal format.\n */\nexport interface ToolCall {\n /** Identifier assigned by the API. */\n id?: string | undefined;\n /** Name of the tool. */\n tool_name: string;\n /** Parsed arguments. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Tool call returned by `chatCompletion`. It carries both the OpenAI shape\n * (`type`, `function`) and the internal shape (`tool_name`, `arguments`).\n */\nexport interface ResponseToolCall {\n /** Identifier assigned by the API. A `tool` message answers the call with it. */\n id: string;\n /** Always `function`. */\n type: 'function';\n /** The call in the OpenAI shape: function name and raw JSON arguments. An empty argument string is sent as `{}`. */\n function: {\n /** Name of the tool to call. */\n name: string;\n /** Arguments as JSON text. */\n arguments: string;\n };\n /** Name of the tool, same as `function.name`. */\n tool_name: string;\n /** Parsed arguments. JSON that is not an object becomes `{}`. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Partial tool call emitted while streaming. `arguments` holds the raw JSON fragment.\n */\nexport interface StreamToolCall {\n /** Identifier assigned by the API. Only the first delta of a call carries it; later deltas have `''`. */\n id: string;\n /** Always `function`. */\n type: 'function';\n /** The delta in the OpenAI shape. `arguments` holds only this delta's JSON fragment. */\n function: {\n /** Name of the tool to call. */\n name: string;\n /** Arguments as JSON text. */\n arguments: string;\n };\n /** Name of the tool when this delta carries it, otherwise `''`. */\n tool_name: string;\n /** Raw JSON fragment of this delta. Concatenate the fragments of a call to get its arguments. */\n arguments: string;\n}\n\n/**\n * Token usage reported by the API.\n */\nexport interface TokenUsage {\n /** Tokens in the generated reply. */\n completion_tokens: number;\n /** Tokens in the request. */\n prompt_tokens: number;\n /** Prompt tokens served from the provider's context cache (reported by DeepSeek). */\n prompt_cache_hit_tokens?: number | undefined;\n /** Prompt tokens not served from the cache. */\n prompt_cache_miss_tokens?: number | undefined;\n /** Sum of prompt and completion tokens. */\n total_tokens: number;\n}\n\n/**\n * Chat message accepted as input. It matches the agent message contract in\n * `@astack-tech/components`, so agent histories can be passed directly.\n */\nexport interface Message {\n /** One of `system`, `user`, `assistant` or `tool` (see `MessageRole`). */\n role: string;\n /** Text content. Omitted content is sent as no content. */\n content?: string | undefined;\n /** Accepted for compatibility. Assistant tool calls are not forwarded to the API. */\n tool_calls?: readonly unknown[] | undefined;\n /** Identifier of the call that a `tool` message answers. */\n tool_call_id?: string | undefined;\n /** Ignored on input. Present so agent histories can be passed unchanged. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Assistant message returned by `chatCompletion`.\n */\nexport interface AssistantMessage {\n /** Always `assistant`. */\n role: 'assistant';\n /** Reply text. Empty when the model only calls tools. */\n content: string;\n /** Tool calls requested by the model, if any. */\n tool_calls?: ResponseToolCall[] | undefined;\n /** Token usage of this request, when the API reports it. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Chunk yielded by `streamChatCompletion`.\n */\nexport interface StreamChunk {\n /** Always `assistant`. */\n role: 'assistant';\n /** Text delta. Empty in chunks that carry tool calls or usage. */\n content: string;\n /** Tool-call deltas in this chunk. */\n tool_calls?: StreamToolCall[] | undefined;\n /** Token usage. Sent once, in the final chunk, when the API reports it. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Per-call options.\n */\nexport interface ChatCompletionOptions {\n /** Tools for this call only. They replace the configured tools. */\n temporaryTools?: unknown[] | undefined;\n}\n\ntype ChatMessageParam = OpenAI.Chat.ChatCompletionMessageParam;\ntype ChatTool = OpenAI.Chat.ChatCompletionTool;\n\ninterface ApiUsage {\n completion_tokens?: number;\n prompt_tokens?: number;\n total_tokens?: number;\n prompt_cache_hit_tokens?: number;\n prompt_cache_miss_tokens?: number;\n}\n\nconst DEFAULT_BASE_URL = 'https://api.openai.com/v1';\nconst DEFAULT_NAME = 'The model API';\nconst DEFAULT_TEMPERATURE = 0.7;\nconst FALLBACK_TEXT = 'API call failed';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isOptionalString(value: unknown): value is string | undefined {\n return value === undefined || typeof value === 'string';\n}\n\nfunction isCount(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value);\n}\n\nfunction isTokenUsage(value: unknown): value is TokenUsage {\n return (\n isRecord(value) &&\n isCount(value['completion_tokens']) &&\n isCount(value['prompt_tokens']) &&\n isCount(value['total_tokens']) &&\n (value['prompt_cache_hit_tokens'] === undefined || isCount(value['prompt_cache_hit_tokens'])) &&\n (value['prompt_cache_miss_tokens'] === undefined || isCount(value['prompt_cache_miss_tokens']))\n );\n}\n\n/** Whether `value` is a `Message`, checked field by field. The role is checked when the message is sent. */\nfunction isMessage(value: unknown): value is Message {\n return (\n isRecord(value) &&\n typeof value['role'] === 'string' &&\n isOptionalString(value['content']) &&\n (value['tool_calls'] === undefined || Array.isArray(value['tool_calls'])) &&\n isOptionalString(value['tool_call_id']) &&\n (value['usage'] === undefined || isTokenUsage(value['usage']))\n );\n}\n\nfunction isMessageList(value: unknown): value is Message[] {\n return Array.isArray(value) && value.every(item => isMessage(item));\n}\n\n/**\n * Converts a `{ name, description, parameters }` object to the OpenAI tool format.\n * Objects already in the OpenAI format are returned unchanged.\n */\nfunction toApiTool(tool: unknown): ChatTool {\n const raw = isRecord(tool) ? tool : {};\n const definition = raw['function'];\n if (raw['type'] === 'function' && isRecord(definition)) {\n const { name, description, parameters, strict } = definition;\n return {\n type: 'function',\n function: {\n name: typeof name === 'string' ? name : '',\n ...(typeof description === 'string' ? { description } : {}),\n ...(isRecord(parameters) ? { parameters } : {}),\n ...(typeof strict === 'boolean' ? { strict } : {}),\n },\n };\n }\n const { name, description, parameters } = raw;\n return {\n type: 'function',\n function: {\n name: typeof name === 'string' ? name : '',\n description: typeof description === 'string' ? description : '',\n parameters: isRecord(parameters) ? parameters : {},\n },\n };\n}\n\n/**\n * Parses tool-call arguments. Anything that is not a JSON object becomes `{}`.\n */\nfunction parseArguments(raw: string): Record<string, unknown> {\n if (raw.trim().length === 0) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n return isRecord(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\nfunction mapUsage(usage: ApiUsage): TokenUsage {\n return {\n completion_tokens: usage.completion_tokens ?? 0,\n prompt_tokens: usage.prompt_tokens ?? 0,\n total_tokens: usage.total_tokens ?? 0,\n ...(usage.prompt_cache_hit_tokens === undefined\n ? {}\n : { prompt_cache_hit_tokens: usage.prompt_cache_hit_tokens }),\n ...(usage.prompt_cache_miss_tokens === undefined\n ? {}\n : { prompt_cache_miss_tokens: usage.prompt_cache_miss_tokens }),\n };\n}\n\n/**\n * Model provider for any API that speaks the OpenAI chat completions protocol:\n * OpenAI itself, and the compatible endpoints of DeepSeek, Qwen, Moonshot, Ollama,\n * vLLM and others. Set `baseURL` and `model` for the provider you use.\n *\n * Ports:\n * - `prompt` (in): a text prompt. The answer is sent on `completion`.\n * - `messages` (in): a list of chat messages. The reply is sent on `message`.\n */\nclass OpenAICompatible extends Component {\n private readonly client: OpenAI;\n private readonly model: string;\n private readonly temperature: number;\n private readonly maxTokens: number | undefined;\n private readonly topP: number | undefined;\n private readonly systemPrompt: string | undefined;\n private readonly tools: unknown[] | undefined;\n private readonly verbose: boolean;\n private readonly name: string;\n\n constructor(config: OpenAICompatibleConfig) {\n super({});\n\n this.client = new OpenAI({\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n dangerouslyAllowBrowser: config.dangerouslyAllowBrowser ?? false,\n });\n\n this.model = config.model;\n this.name = config.name ?? DEFAULT_NAME;\n this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;\n this.maxTokens = config.maxTokens;\n this.topP = config.topP;\n this.systemPrompt = config.systemPrompt;\n this.verbose = config.verbose ?? false;\n this.tools =\n config.rawTools !== undefined && config.rawTools.length > 0\n ? config.rawTools.map(toApiTool)\n : config.tools;\n\n Component.Port.I('prompt').attach(this);\n Component.Port.I('messages').attach(this);\n Component.Port.O('completion').attach(this);\n Component.Port.O('message').attach(this);\n }\n\n /**\n * Generates a completion for a single prompt.\n *\n * @returns The text answer, or the JSON-encoded assistant message when it contains tool calls.\n */\n async generateCompletion(prompt: string): Promise<string> {\n const response = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: [...this.systemMessages(), { role: 'user', content: prompt }],\n ...this.toolsParam(this.tools),\n });\n\n const message = this.firstMessage(response);\n if (message.tool_calls !== undefined && message.tool_calls.length > 0) {\n return JSON.stringify(message);\n }\n return message.content ?? '';\n }\n\n /**\n * Sends a conversation and returns the assistant reply.\n */\n async chatCompletion(\n messages: Message[],\n options?: ChatCompletionOptions\n ): Promise<AssistantMessage> {\n const response = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: this.formatMessages(messages),\n ...this.toolsParam(options?.temporaryTools ?? this.tools),\n });\n\n const message = this.firstMessage(response);\n const result: AssistantMessage = { role: 'assistant', content: message.content ?? '' };\n\n if (response.usage !== undefined) {\n result.usage = mapUsage(response.usage);\n }\n\n if (message.tool_calls !== undefined && message.tool_calls.length > 0) {\n result.tool_calls = message.tool_calls.map(toolCall => {\n if (toolCall.type !== 'function') {\n throw new Error(`Unsupported tool call type: ${toolCall.type}`);\n }\n const rawArguments = toolCall.function.arguments;\n return {\n id: toolCall.id,\n type: 'function',\n function: {\n name: toolCall.function.name,\n arguments: rawArguments.length > 0 ? rawArguments : '{}',\n },\n tool_name: toolCall.function.name,\n arguments: parseArguments(rawArguments),\n };\n });\n }\n\n return result;\n }\n\n /**\n * Streams the assistant reply. Each chunk holds a text delta, tool-call deltas, or usage.\n */\n async *streamChatCompletion(\n messages: Message[],\n options?: ChatCompletionOptions\n ): AsyncGenerator<StreamChunk> {\n const stream = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: this.formatMessages(messages),\n ...this.toolsParam(options?.temporaryTools ?? this.tools),\n stream: true,\n });\n\n for await (const chunk of stream) {\n const delta = chunk.choices[0]?.delta;\n\n const text = delta?.content ?? '';\n if (text !== '') {\n yield { role: 'assistant', content: text };\n }\n\n if (delta?.tool_calls) {\n yield {\n role: 'assistant',\n content: '',\n tool_calls: delta.tool_calls.map(toolCall => {\n const name = toolCall.function?.name ?? '';\n const args = toolCall.function?.arguments ?? '';\n return {\n id: toolCall.id ?? '',\n type: 'function',\n function: { name, arguments: args },\n tool_name: name,\n arguments: args,\n };\n }),\n };\n }\n\n if (chunk.usage) {\n yield { role: 'assistant', content: '', usage: mapUsage(chunk.usage) };\n }\n }\n }\n\n /**\n * Runs the model outside a pipeline.\n *\n * @param input - A prompt string or a list of messages.\n */\n override run(input: string | Message[]): Promise<string | AssistantMessage> {\n return typeof input === 'string' ? this.generateCompletion(input) : this.chatCompletion(input);\n }\n\n /**\n * Connects the `prompt` and `messages` input ports to the model.\n * API errors produce a fallback value so the pipeline keeps flowing. Input of the wrong\n * type fails the pipeline run with a `TypeError` before the API is called.\n */\n override _transform($i: Port, $o: Port): void {\n $i('prompt').receive(prompt => {\n if (typeof prompt !== 'string') {\n throw new TypeError(`${this.name}: the prompt port expects a string.`);\n }\n return this.generateCompletion(prompt).then(\n completion => {\n $o('completion').send(completion);\n },\n (error: unknown) => {\n this.logError(error);\n $o('completion').send(FALLBACK_TEXT);\n }\n );\n });\n\n $i('messages').receive(messages => {\n if (!isMessageList(messages)) {\n throw new TypeError(`${this.name}: the messages port expects an array of messages.`);\n }\n return this.chatCompletion(messages).then(\n message => {\n $o('message').send(message);\n },\n (error: unknown) => {\n this.logError(error);\n $o('message').send({ role: 'assistant', content: FALLBACK_TEXT });\n }\n );\n });\n }\n\n private baseRequest(): {\n model: string;\n temperature: number;\n max_tokens?: number;\n top_p?: number;\n } {\n return {\n model: this.model,\n temperature: this.temperature,\n ...(this.maxTokens === undefined ? {} : { max_tokens: this.maxTokens }),\n ...(this.topP === undefined ? {} : { top_p: this.topP }),\n };\n }\n\n private toolsParam(tools: unknown[] | undefined): { tools?: ChatTool[] } {\n return tools !== undefined && tools.length > 0 ? { tools: tools.map(toApiTool) } : {};\n }\n\n private systemMessages(): ChatMessageParam[] {\n return this.systemPrompt === undefined || this.systemPrompt === ''\n ? []\n : [{ role: 'system', content: this.systemPrompt }];\n }\n\n /**\n * Converts input messages to API messages. Assistant `tool_calls` are not forwarded:\n * agents send tool results back as user messages, and the API rejects assistant tool\n * calls that are not followed by tool messages.\n */\n private formatMessages(messages: Message[]): ChatMessageParam[] {\n return [...this.systemMessages(), ...messages.map(message => this.toApiMessage(message))];\n }\n\n /**\n * Converts one input message to its API shape.\n *\n * @throws Error when the role is not one of `MessageRole`, or a `tool` message has no\n * `tool_call_id`. The API would reject both.\n */\n private toApiMessage(message: Message): ChatMessageParam {\n const content = message.content ?? '';\n switch (message.role) {\n case 'system':\n return { role: 'system', content };\n case 'user':\n return { role: 'user', content };\n case 'assistant':\n return message.content === undefined\n ? { role: 'assistant' }\n : { role: 'assistant', content: message.content };\n case 'tool':\n if (message.tool_call_id === undefined || message.tool_call_id === '') {\n throw new Error(\n `${this.name}: a tool message needs the tool_call_id of the call it answers.`\n );\n }\n return { role: 'tool', content, tool_call_id: message.tool_call_id };\n default:\n throw new Error(\n `${this.name}: unsupported message role '${message.role}'. Use system, user, assistant or tool.`\n );\n }\n }\n\n private firstMessage(response: OpenAI.Chat.ChatCompletion): OpenAI.Chat.ChatCompletionMessage {\n const choice = response.choices[0];\n if (choice === undefined) {\n throw new Error(`${this.name} returned no choices.`);\n }\n return choice.message;\n }\n\n private logError(error: unknown): void {\n if (this.verbose) {\n // eslint-disable-next-line no-console -- opt-in diagnostics controlled by `verbose`.\n console.error(`[${this.name}] API call failed:`, error);\n }\n }\n}\n\nexport default OpenAICompatible;\n","import OpenAICompatible from '../openai-compatible';\nimport type { OpenAICompatibleConfig } from '../openai-compatible';\n\nexport type {\n AssistantMessage,\n ChatCompletionOptions,\n Message,\n MessageRole,\n RawTool,\n ResponseToolCall,\n StreamChunk,\n StreamToolCall,\n TokenUsage,\n ToolCall,\n} from '../openai-compatible';\n\nconst DEFAULT_BASE_URL = 'https://api.deepseek.com/v1';\nconst DEFAULT_MODEL = 'deepseek-chat';\n\n/**\n * Options for the DeepSeek model provider. They are the options of\n * `OpenAICompatible`, with DeepSeek's endpoint and model as defaults.\n */\nexport interface DeepseekConfig extends Omit<OpenAICompatibleConfig, 'model' | 'name'> {\n /** Model name. Defaults to `deepseek-chat`. */\n model?: string | undefined;\n /** API base URL. Defaults to `https://api.deepseek.com/v1`. */\n baseURL?: string | undefined;\n}\n\n/**\n * DeepSeek model provider: `OpenAICompatible` set up for the DeepSeek API.\n *\n * Ports:\n * - `prompt` (in): a text prompt. The answer is sent on `completion`.\n * - `messages` (in): a list of chat messages. The reply is sent on `message`.\n */\nclass Deepseek extends OpenAICompatible {\n constructor(config: DeepseekConfig) {\n super({\n ...config,\n model: config.model ?? DEFAULT_MODEL,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n name: 'DeepSeek',\n });\n }\n}\n\nexport default Deepseek;\n"],"mappings":"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","names":["name","description","parameters","OpenAI","DEFAULT_BASE_URL"]}
@@ -205,7 +205,8 @@ declare class OpenAICompatible extends Component {
205
205
  run(input: string | Message[]): Promise<string | AssistantMessage>;
206
206
  /**
207
207
  * Connects the `prompt` and `messages` input ports to the model.
208
- * API errors produce a fallback value so the pipeline keeps flowing.
208
+ * API errors produce a fallback value so the pipeline keeps flowing. Input of the wrong
209
+ * type fails the pipeline run with a `TypeError` before the API is called.
209
210
  */
210
211
  _transform($i: Port, $o: Port): void;
211
212
  private baseRequest;
@@ -205,7 +205,8 @@ declare class OpenAICompatible extends Component {
205
205
  run(input: string | Message[]): Promise<string | AssistantMessage>;
206
206
  /**
207
207
  * Connects the `prompt` and `messages` input ports to the model.
208
- * API errors produce a fallback value so the pipeline keeps flowing.
208
+ * API errors produce a fallback value so the pipeline keeps flowing. Input of the wrong
209
+ * type fails the pipeline run with a `TypeError` before the API is called.
209
210
  */
210
211
  _transform($i: Port, $o: Port): void;
211
212
  private baseRequest;
@@ -8,6 +8,21 @@ var FALLBACK_TEXT = "API call failed";
8
8
  function isRecord(value) {
9
9
  return typeof value === "object" && value !== null && !Array.isArray(value);
10
10
  }
11
+ function isOptionalString(value) {
12
+ return value === void 0 || typeof value === "string";
13
+ }
14
+ function isCount(value) {
15
+ return typeof value === "number" && Number.isFinite(value);
16
+ }
17
+ function isTokenUsage(value) {
18
+ return isRecord(value) && isCount(value["completion_tokens"]) && isCount(value["prompt_tokens"]) && isCount(value["total_tokens"]) && (value["prompt_cache_hit_tokens"] === void 0 || isCount(value["prompt_cache_hit_tokens"])) && (value["prompt_cache_miss_tokens"] === void 0 || isCount(value["prompt_cache_miss_tokens"]));
19
+ }
20
+ function isMessage(value) {
21
+ return isRecord(value) && typeof value["role"] === "string" && isOptionalString(value["content"]) && (value["tool_calls"] === void 0 || Array.isArray(value["tool_calls"])) && isOptionalString(value["tool_call_id"]) && (value["usage"] === void 0 || isTokenUsage(value["usage"]));
22
+ }
23
+ function isMessageList(value) {
24
+ return Array.isArray(value) && value.every((item) => isMessage(item));
25
+ }
11
26
  function toApiTool(tool) {
12
27
  const raw = isRecord(tool) ? tool : {};
13
28
  const definition = raw["function"];
@@ -182,11 +197,15 @@ var OpenAICompatible = class extends Component {
182
197
  }
183
198
  /**
184
199
  * Connects the `prompt` and `messages` input ports to the model.
185
- * API errors produce a fallback value so the pipeline keeps flowing.
200
+ * API errors produce a fallback value so the pipeline keeps flowing. Input of the wrong
201
+ * type fails the pipeline run with a `TypeError` before the API is called.
186
202
  */
187
203
  _transform($i, $o) {
188
204
  $i("prompt").receive((prompt) => {
189
- this.generateCompletion(prompt).then(
205
+ if (typeof prompt !== "string") {
206
+ throw new TypeError(`${this.name}: the prompt port expects a string.`);
207
+ }
208
+ return this.generateCompletion(prompt).then(
190
209
  (completion) => {
191
210
  $o("completion").send(completion);
192
211
  },
@@ -197,7 +216,10 @@ var OpenAICompatible = class extends Component {
197
216
  );
198
217
  });
199
218
  $i("messages").receive((messages) => {
200
- this.chatCompletion(messages).then(
219
+ if (!isMessageList(messages)) {
220
+ throw new TypeError(`${this.name}: the messages port expects an array of messages.`);
221
+ }
222
+ return this.chatCompletion(messages).then(
201
223
  (message) => {
202
224
  $o("message").send(message);
203
225
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/model-provider/openai-compatible/index.ts","../../src/model-provider/deepseek/index.ts"],"sourcesContent":["import { Component } from '@astack-tech/core';\nimport type { Port } from '@astack-tech/core';\nimport OpenAI from 'openai';\n\n/**\n * Options for a provider that speaks the OpenAI chat completions API.\n */\nexport interface OpenAICompatibleConfig {\n /** API key of the provider. */\n apiKey: string;\n /** Model name, for example `gpt-4o-mini`, `qwen-plus` or `llama3.1`. */\n model: string;\n /** API base URL. Defaults to `https://api.openai.com/v1`. */\n baseURL?: string | undefined;\n /** Provider name used in errors and logs. Defaults to `The model API`. */\n name?: string | undefined;\n /** Sampling temperature. Defaults to `0.7`. */\n temperature?: number | undefined;\n /** Maximum number of generated tokens. */\n maxTokens?: number | undefined;\n /** Nucleus sampling probability. */\n topP?: number | undefined;\n /** System prompt added before every request. */\n systemPrompt?: string | undefined;\n /**\n * Tools given as `{ name, description, parameters }` objects.\n * When this list is not empty, it takes precedence over `tools`.\n */\n rawTools?: RawTool[] | undefined;\n /** Tools in the OpenAI-compatible `{ type: 'function', function }` format. */\n tools?: unknown[] | undefined;\n /** Allows the OpenAI client to run in a browser. Defaults to `false`. */\n dangerouslyAllowBrowser?: boolean | undefined;\n /** Logs API errors to the console. Defaults to `false`. */\n verbose?: boolean | undefined;\n}\n\n/**\n * Tool description accepted by `rawTools` and `temporaryTools`.\n */\nexport interface RawTool {\n /** Unique tool name. */\n name: string;\n /** Description that tells the model when to use the tool. */\n description: string;\n /** JSON Schema of the arguments. Defaults to `{}`. */\n parameters?: Record<string, unknown> | undefined;\n}\n\n/**\n * Role of a chat message.\n */\nexport type MessageRole = 'system' | 'user' | 'assistant' | 'tool';\n\n/**\n * Tool call in the internal format.\n */\nexport interface ToolCall {\n /** Identifier assigned by the API. */\n id?: string | undefined;\n /** Name of the tool. */\n tool_name: string;\n /** Parsed arguments. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Tool call returned by `chatCompletion`. It carries both the OpenAI shape\n * (`type`, `function`) and the internal shape (`tool_name`, `arguments`).\n */\nexport interface ResponseToolCall {\n /** Identifier assigned by the API. A `tool` message answers the call with it. */\n id: string;\n /** Always `function`. */\n type: 'function';\n /** The call in the OpenAI shape: function name and raw JSON arguments. An empty argument string is sent as `{}`. */\n function: {\n /** Name of the tool to call. */\n name: string;\n /** Arguments as JSON text. */\n arguments: string;\n };\n /** Name of the tool, same as `function.name`. */\n tool_name: string;\n /** Parsed arguments. JSON that is not an object becomes `{}`. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Partial tool call emitted while streaming. `arguments` holds the raw JSON fragment.\n */\nexport interface StreamToolCall {\n /** Identifier assigned by the API. Only the first delta of a call carries it; later deltas have `''`. */\n id: string;\n /** Always `function`. */\n type: 'function';\n /** The delta in the OpenAI shape. `arguments` holds only this delta's JSON fragment. */\n function: {\n /** Name of the tool to call. */\n name: string;\n /** Arguments as JSON text. */\n arguments: string;\n };\n /** Name of the tool when this delta carries it, otherwise `''`. */\n tool_name: string;\n /** Raw JSON fragment of this delta. Concatenate the fragments of a call to get its arguments. */\n arguments: string;\n}\n\n/**\n * Token usage reported by the API.\n */\nexport interface TokenUsage {\n /** Tokens in the generated reply. */\n completion_tokens: number;\n /** Tokens in the request. */\n prompt_tokens: number;\n /** Prompt tokens served from the provider's context cache (reported by DeepSeek). */\n prompt_cache_hit_tokens?: number | undefined;\n /** Prompt tokens not served from the cache. */\n prompt_cache_miss_tokens?: number | undefined;\n /** Sum of prompt and completion tokens. */\n total_tokens: number;\n}\n\n/**\n * Chat message accepted as input. It matches the agent message contract in\n * `@astack-tech/components`, so agent histories can be passed directly.\n */\nexport interface Message {\n /** One of `system`, `user`, `assistant` or `tool` (see `MessageRole`). */\n role: string;\n /** Text content. Omitted content is sent as no content. */\n content?: string | undefined;\n /** Accepted for compatibility. Assistant tool calls are not forwarded to the API. */\n tool_calls?: readonly unknown[] | undefined;\n /** Identifier of the call that a `tool` message answers. */\n tool_call_id?: string | undefined;\n /** Ignored on input. Present so agent histories can be passed unchanged. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Assistant message returned by `chatCompletion`.\n */\nexport interface AssistantMessage {\n /** Always `assistant`. */\n role: 'assistant';\n /** Reply text. Empty when the model only calls tools. */\n content: string;\n /** Tool calls requested by the model, if any. */\n tool_calls?: ResponseToolCall[] | undefined;\n /** Token usage of this request, when the API reports it. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Chunk yielded by `streamChatCompletion`.\n */\nexport interface StreamChunk {\n /** Always `assistant`. */\n role: 'assistant';\n /** Text delta. Empty in chunks that carry tool calls or usage. */\n content: string;\n /** Tool-call deltas in this chunk. */\n tool_calls?: StreamToolCall[] | undefined;\n /** Token usage. Sent once, in the final chunk, when the API reports it. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Per-call options.\n */\nexport interface ChatCompletionOptions {\n /** Tools for this call only. They replace the configured tools. */\n temporaryTools?: unknown[] | undefined;\n}\n\ntype ChatMessageParam = OpenAI.Chat.ChatCompletionMessageParam;\ntype ChatTool = OpenAI.Chat.ChatCompletionTool;\n\ninterface ApiUsage {\n completion_tokens?: number;\n prompt_tokens?: number;\n total_tokens?: number;\n prompt_cache_hit_tokens?: number;\n prompt_cache_miss_tokens?: number;\n}\n\nconst DEFAULT_BASE_URL = 'https://api.openai.com/v1';\nconst DEFAULT_NAME = 'The model API';\nconst DEFAULT_TEMPERATURE = 0.7;\nconst FALLBACK_TEXT = 'API call failed';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * Converts a `{ name, description, parameters }` object to the OpenAI tool format.\n * Objects already in the OpenAI format are returned unchanged.\n */\nfunction toApiTool(tool: unknown): ChatTool {\n const raw = isRecord(tool) ? tool : {};\n const definition = raw['function'];\n if (raw['type'] === 'function' && isRecord(definition)) {\n const { name, description, parameters, strict } = definition;\n return {\n type: 'function',\n function: {\n name: typeof name === 'string' ? name : '',\n ...(typeof description === 'string' ? { description } : {}),\n ...(isRecord(parameters) ? { parameters } : {}),\n ...(typeof strict === 'boolean' ? { strict } : {}),\n },\n };\n }\n const { name, description, parameters } = raw;\n return {\n type: 'function',\n function: {\n name: typeof name === 'string' ? name : '',\n description: typeof description === 'string' ? description : '',\n parameters: isRecord(parameters) ? parameters : {},\n },\n };\n}\n\n/**\n * Parses tool-call arguments. Anything that is not a JSON object becomes `{}`.\n */\nfunction parseArguments(raw: string): Record<string, unknown> {\n if (raw.trim().length === 0) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n return isRecord(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\nfunction mapUsage(usage: ApiUsage): TokenUsage {\n return {\n completion_tokens: usage.completion_tokens ?? 0,\n prompt_tokens: usage.prompt_tokens ?? 0,\n total_tokens: usage.total_tokens ?? 0,\n ...(usage.prompt_cache_hit_tokens === undefined\n ? {}\n : { prompt_cache_hit_tokens: usage.prompt_cache_hit_tokens }),\n ...(usage.prompt_cache_miss_tokens === undefined\n ? {}\n : { prompt_cache_miss_tokens: usage.prompt_cache_miss_tokens }),\n };\n}\n\n/**\n * Model provider for any API that speaks the OpenAI chat completions protocol:\n * OpenAI itself, and the compatible endpoints of DeepSeek, Qwen, Moonshot, Ollama,\n * vLLM and others. Set `baseURL` and `model` for the provider you use.\n *\n * Ports:\n * - `prompt` (in): a text prompt. The answer is sent on `completion`.\n * - `messages` (in): a list of chat messages. The reply is sent on `message`.\n */\nclass OpenAICompatible extends Component {\n private readonly client: OpenAI;\n private readonly model: string;\n private readonly temperature: number;\n private readonly maxTokens: number | undefined;\n private readonly topP: number | undefined;\n private readonly systemPrompt: string | undefined;\n private readonly tools: unknown[] | undefined;\n private readonly verbose: boolean;\n private readonly name: string;\n\n constructor(config: OpenAICompatibleConfig) {\n super({});\n\n this.client = new OpenAI({\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n dangerouslyAllowBrowser: config.dangerouslyAllowBrowser ?? false,\n });\n\n this.model = config.model;\n this.name = config.name ?? DEFAULT_NAME;\n this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;\n this.maxTokens = config.maxTokens;\n this.topP = config.topP;\n this.systemPrompt = config.systemPrompt;\n this.verbose = config.verbose ?? false;\n this.tools =\n config.rawTools !== undefined && config.rawTools.length > 0\n ? config.rawTools.map(toApiTool)\n : config.tools;\n\n Component.Port.I('prompt').attach(this);\n Component.Port.I('messages').attach(this);\n Component.Port.O('completion').attach(this);\n Component.Port.O('message').attach(this);\n }\n\n /**\n * Generates a completion for a single prompt.\n *\n * @returns The text answer, or the JSON-encoded assistant message when it contains tool calls.\n */\n async generateCompletion(prompt: string): Promise<string> {\n const response = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: [...this.systemMessages(), { role: 'user', content: prompt }],\n ...this.toolsParam(this.tools),\n });\n\n const message = this.firstMessage(response);\n if (message.tool_calls !== undefined && message.tool_calls.length > 0) {\n return JSON.stringify(message);\n }\n return message.content ?? '';\n }\n\n /**\n * Sends a conversation and returns the assistant reply.\n */\n async chatCompletion(\n messages: Message[],\n options?: ChatCompletionOptions\n ): Promise<AssistantMessage> {\n const response = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: this.formatMessages(messages),\n ...this.toolsParam(options?.temporaryTools ?? this.tools),\n });\n\n const message = this.firstMessage(response);\n const result: AssistantMessage = { role: 'assistant', content: message.content ?? '' };\n\n if (response.usage !== undefined) {\n result.usage = mapUsage(response.usage);\n }\n\n if (message.tool_calls !== undefined && message.tool_calls.length > 0) {\n result.tool_calls = message.tool_calls.map(toolCall => {\n if (toolCall.type !== 'function') {\n throw new Error(`Unsupported tool call type: ${toolCall.type}`);\n }\n const rawArguments = toolCall.function.arguments;\n return {\n id: toolCall.id,\n type: 'function',\n function: {\n name: toolCall.function.name,\n arguments: rawArguments.length > 0 ? rawArguments : '{}',\n },\n tool_name: toolCall.function.name,\n arguments: parseArguments(rawArguments),\n };\n });\n }\n\n return result;\n }\n\n /**\n * Streams the assistant reply. Each chunk holds a text delta, tool-call deltas, or usage.\n */\n async *streamChatCompletion(\n messages: Message[],\n options?: ChatCompletionOptions\n ): AsyncGenerator<StreamChunk> {\n const stream = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: this.formatMessages(messages),\n ...this.toolsParam(options?.temporaryTools ?? this.tools),\n stream: true,\n });\n\n for await (const chunk of stream) {\n const delta = chunk.choices[0]?.delta;\n\n const text = delta?.content ?? '';\n if (text !== '') {\n yield { role: 'assistant', content: text };\n }\n\n if (delta?.tool_calls) {\n yield {\n role: 'assistant',\n content: '',\n tool_calls: delta.tool_calls.map(toolCall => {\n const name = toolCall.function?.name ?? '';\n const args = toolCall.function?.arguments ?? '';\n return {\n id: toolCall.id ?? '',\n type: 'function',\n function: { name, arguments: args },\n tool_name: name,\n arguments: args,\n };\n }),\n };\n }\n\n if (chunk.usage) {\n yield { role: 'assistant', content: '', usage: mapUsage(chunk.usage) };\n }\n }\n }\n\n /**\n * Runs the model outside a pipeline.\n *\n * @param input - A prompt string or a list of messages.\n */\n override run(input: string | Message[]): Promise<string | AssistantMessage> {\n return typeof input === 'string' ? this.generateCompletion(input) : this.chatCompletion(input);\n }\n\n /**\n * Connects the `prompt` and `messages` input ports to the model.\n * API errors produce a fallback value so the pipeline keeps flowing.\n */\n override _transform($i: Port, $o: Port): void {\n $i('prompt').receive((prompt: string) => {\n this.generateCompletion(prompt).then(\n completion => {\n $o('completion').send(completion);\n },\n (error: unknown) => {\n this.logError(error);\n $o('completion').send(FALLBACK_TEXT);\n }\n );\n });\n\n $i('messages').receive((messages: Message[]) => {\n this.chatCompletion(messages).then(\n message => {\n $o('message').send(message);\n },\n (error: unknown) => {\n this.logError(error);\n $o('message').send({ role: 'assistant', content: FALLBACK_TEXT });\n }\n );\n });\n }\n\n private baseRequest(): {\n model: string;\n temperature: number;\n max_tokens?: number;\n top_p?: number;\n } {\n return {\n model: this.model,\n temperature: this.temperature,\n ...(this.maxTokens === undefined ? {} : { max_tokens: this.maxTokens }),\n ...(this.topP === undefined ? {} : { top_p: this.topP }),\n };\n }\n\n private toolsParam(tools: unknown[] | undefined): { tools?: ChatTool[] } {\n return tools !== undefined && tools.length > 0 ? { tools: tools.map(toApiTool) } : {};\n }\n\n private systemMessages(): ChatMessageParam[] {\n return this.systemPrompt === undefined || this.systemPrompt === ''\n ? []\n : [{ role: 'system', content: this.systemPrompt }];\n }\n\n /**\n * Converts input messages to API messages. Assistant `tool_calls` are not forwarded:\n * agents send tool results back as user messages, and the API rejects assistant tool\n * calls that are not followed by tool messages.\n */\n private formatMessages(messages: Message[]): ChatMessageParam[] {\n return [...this.systemMessages(), ...messages.map(message => this.toApiMessage(message))];\n }\n\n /**\n * Converts one input message to its API shape.\n *\n * @throws Error when the role is not one of `MessageRole`, or a `tool` message has no\n * `tool_call_id`. The API would reject both.\n */\n private toApiMessage(message: Message): ChatMessageParam {\n const content = message.content ?? '';\n switch (message.role) {\n case 'system':\n return { role: 'system', content };\n case 'user':\n return { role: 'user', content };\n case 'assistant':\n return message.content === undefined\n ? { role: 'assistant' }\n : { role: 'assistant', content: message.content };\n case 'tool':\n if (message.tool_call_id === undefined || message.tool_call_id === '') {\n throw new Error(\n `${this.name}: a tool message needs the tool_call_id of the call it answers.`\n );\n }\n return { role: 'tool', content, tool_call_id: message.tool_call_id };\n default:\n throw new Error(\n `${this.name}: unsupported message role '${message.role}'. Use system, user, assistant or tool.`\n );\n }\n }\n\n private firstMessage(response: OpenAI.Chat.ChatCompletion): OpenAI.Chat.ChatCompletionMessage {\n const choice = response.choices[0];\n if (choice === undefined) {\n throw new Error(`${this.name} returned no choices.`);\n }\n return choice.message;\n }\n\n private logError(error: unknown): void {\n if (this.verbose) {\n // eslint-disable-next-line no-console -- opt-in diagnostics controlled by `verbose`.\n console.error(`[${this.name}] API call failed:`, error);\n }\n }\n}\n\nexport default OpenAICompatible;\n","import OpenAICompatible from '../openai-compatible';\nimport type { OpenAICompatibleConfig } from '../openai-compatible';\n\nexport type {\n AssistantMessage,\n ChatCompletionOptions,\n Message,\n MessageRole,\n RawTool,\n ResponseToolCall,\n StreamChunk,\n StreamToolCall,\n TokenUsage,\n ToolCall,\n} from '../openai-compatible';\n\nconst DEFAULT_BASE_URL = 'https://api.deepseek.com/v1';\nconst DEFAULT_MODEL = 'deepseek-chat';\n\n/**\n * Options for the DeepSeek model provider. They are the options of\n * `OpenAICompatible`, with DeepSeek's endpoint and model as defaults.\n */\nexport interface DeepseekConfig extends Omit<OpenAICompatibleConfig, 'model' | 'name'> {\n /** Model name. Defaults to `deepseek-chat`. */\n model?: string | undefined;\n /** API base URL. Defaults to `https://api.deepseek.com/v1`. */\n baseURL?: string | undefined;\n}\n\n/**\n * DeepSeek model provider: `OpenAICompatible` set up for the DeepSeek API.\n *\n * Ports:\n * - `prompt` (in): a text prompt. The answer is sent on `completion`.\n * - `messages` (in): a list of chat messages. The reply is sent on `message`.\n */\nclass Deepseek extends OpenAICompatible {\n constructor(config: DeepseekConfig) {\n super({\n ...config,\n model: config.model ?? DEFAULT_MODEL,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n name: 'DeepSeek',\n });\n }\n}\n\nexport default Deepseek;\n"],"mappings":"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","names":["name","description","parameters","DEFAULT_BASE_URL"]}
1
+ {"version":3,"sources":["../../src/model-provider/openai-compatible/index.ts","../../src/model-provider/deepseek/index.ts"],"sourcesContent":["import { Component } from '@astack-tech/core';\nimport type { Port } from '@astack-tech/core';\nimport OpenAI from 'openai';\n\n/**\n * Options for a provider that speaks the OpenAI chat completions API.\n */\nexport interface OpenAICompatibleConfig {\n /** API key of the provider. */\n apiKey: string;\n /** Model name, for example `gpt-4o-mini`, `qwen-plus` or `llama3.1`. */\n model: string;\n /** API base URL. Defaults to `https://api.openai.com/v1`. */\n baseURL?: string | undefined;\n /** Provider name used in errors and logs. Defaults to `The model API`. */\n name?: string | undefined;\n /** Sampling temperature. Defaults to `0.7`. */\n temperature?: number | undefined;\n /** Maximum number of generated tokens. */\n maxTokens?: number | undefined;\n /** Nucleus sampling probability. */\n topP?: number | undefined;\n /** System prompt added before every request. */\n systemPrompt?: string | undefined;\n /**\n * Tools given as `{ name, description, parameters }` objects.\n * When this list is not empty, it takes precedence over `tools`.\n */\n rawTools?: RawTool[] | undefined;\n /** Tools in the OpenAI-compatible `{ type: 'function', function }` format. */\n tools?: unknown[] | undefined;\n /** Allows the OpenAI client to run in a browser. Defaults to `false`. */\n dangerouslyAllowBrowser?: boolean | undefined;\n /** Logs API errors to the console. Defaults to `false`. */\n verbose?: boolean | undefined;\n}\n\n/**\n * Tool description accepted by `rawTools` and `temporaryTools`.\n */\nexport interface RawTool {\n /** Unique tool name. */\n name: string;\n /** Description that tells the model when to use the tool. */\n description: string;\n /** JSON Schema of the arguments. Defaults to `{}`. */\n parameters?: Record<string, unknown> | undefined;\n}\n\n/**\n * Role of a chat message.\n */\nexport type MessageRole = 'system' | 'user' | 'assistant' | 'tool';\n\n/**\n * Tool call in the internal format.\n */\nexport interface ToolCall {\n /** Identifier assigned by the API. */\n id?: string | undefined;\n /** Name of the tool. */\n tool_name: string;\n /** Parsed arguments. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Tool call returned by `chatCompletion`. It carries both the OpenAI shape\n * (`type`, `function`) and the internal shape (`tool_name`, `arguments`).\n */\nexport interface ResponseToolCall {\n /** Identifier assigned by the API. A `tool` message answers the call with it. */\n id: string;\n /** Always `function`. */\n type: 'function';\n /** The call in the OpenAI shape: function name and raw JSON arguments. An empty argument string is sent as `{}`. */\n function: {\n /** Name of the tool to call. */\n name: string;\n /** Arguments as JSON text. */\n arguments: string;\n };\n /** Name of the tool, same as `function.name`. */\n tool_name: string;\n /** Parsed arguments. JSON that is not an object becomes `{}`. */\n arguments: Record<string, unknown>;\n}\n\n/**\n * Partial tool call emitted while streaming. `arguments` holds the raw JSON fragment.\n */\nexport interface StreamToolCall {\n /** Identifier assigned by the API. Only the first delta of a call carries it; later deltas have `''`. */\n id: string;\n /** Always `function`. */\n type: 'function';\n /** The delta in the OpenAI shape. `arguments` holds only this delta's JSON fragment. */\n function: {\n /** Name of the tool to call. */\n name: string;\n /** Arguments as JSON text. */\n arguments: string;\n };\n /** Name of the tool when this delta carries it, otherwise `''`. */\n tool_name: string;\n /** Raw JSON fragment of this delta. Concatenate the fragments of a call to get its arguments. */\n arguments: string;\n}\n\n/**\n * Token usage reported by the API.\n */\nexport interface TokenUsage {\n /** Tokens in the generated reply. */\n completion_tokens: number;\n /** Tokens in the request. */\n prompt_tokens: number;\n /** Prompt tokens served from the provider's context cache (reported by DeepSeek). */\n prompt_cache_hit_tokens?: number | undefined;\n /** Prompt tokens not served from the cache. */\n prompt_cache_miss_tokens?: number | undefined;\n /** Sum of prompt and completion tokens. */\n total_tokens: number;\n}\n\n/**\n * Chat message accepted as input. It matches the agent message contract in\n * `@astack-tech/components`, so agent histories can be passed directly.\n */\nexport interface Message {\n /** One of `system`, `user`, `assistant` or `tool` (see `MessageRole`). */\n role: string;\n /** Text content. Omitted content is sent as no content. */\n content?: string | undefined;\n /** Accepted for compatibility. Assistant tool calls are not forwarded to the API. */\n tool_calls?: readonly unknown[] | undefined;\n /** Identifier of the call that a `tool` message answers. */\n tool_call_id?: string | undefined;\n /** Ignored on input. Present so agent histories can be passed unchanged. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Assistant message returned by `chatCompletion`.\n */\nexport interface AssistantMessage {\n /** Always `assistant`. */\n role: 'assistant';\n /** Reply text. Empty when the model only calls tools. */\n content: string;\n /** Tool calls requested by the model, if any. */\n tool_calls?: ResponseToolCall[] | undefined;\n /** Token usage of this request, when the API reports it. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Chunk yielded by `streamChatCompletion`.\n */\nexport interface StreamChunk {\n /** Always `assistant`. */\n role: 'assistant';\n /** Text delta. Empty in chunks that carry tool calls or usage. */\n content: string;\n /** Tool-call deltas in this chunk. */\n tool_calls?: StreamToolCall[] | undefined;\n /** Token usage. Sent once, in the final chunk, when the API reports it. */\n usage?: TokenUsage | undefined;\n}\n\n/**\n * Per-call options.\n */\nexport interface ChatCompletionOptions {\n /** Tools for this call only. They replace the configured tools. */\n temporaryTools?: unknown[] | undefined;\n}\n\ntype ChatMessageParam = OpenAI.Chat.ChatCompletionMessageParam;\ntype ChatTool = OpenAI.Chat.ChatCompletionTool;\n\ninterface ApiUsage {\n completion_tokens?: number;\n prompt_tokens?: number;\n total_tokens?: number;\n prompt_cache_hit_tokens?: number;\n prompt_cache_miss_tokens?: number;\n}\n\nconst DEFAULT_BASE_URL = 'https://api.openai.com/v1';\nconst DEFAULT_NAME = 'The model API';\nconst DEFAULT_TEMPERATURE = 0.7;\nconst FALLBACK_TEXT = 'API call failed';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isOptionalString(value: unknown): value is string | undefined {\n return value === undefined || typeof value === 'string';\n}\n\nfunction isCount(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value);\n}\n\nfunction isTokenUsage(value: unknown): value is TokenUsage {\n return (\n isRecord(value) &&\n isCount(value['completion_tokens']) &&\n isCount(value['prompt_tokens']) &&\n isCount(value['total_tokens']) &&\n (value['prompt_cache_hit_tokens'] === undefined || isCount(value['prompt_cache_hit_tokens'])) &&\n (value['prompt_cache_miss_tokens'] === undefined || isCount(value['prompt_cache_miss_tokens']))\n );\n}\n\n/** Whether `value` is a `Message`, checked field by field. The role is checked when the message is sent. */\nfunction isMessage(value: unknown): value is Message {\n return (\n isRecord(value) &&\n typeof value['role'] === 'string' &&\n isOptionalString(value['content']) &&\n (value['tool_calls'] === undefined || Array.isArray(value['tool_calls'])) &&\n isOptionalString(value['tool_call_id']) &&\n (value['usage'] === undefined || isTokenUsage(value['usage']))\n );\n}\n\nfunction isMessageList(value: unknown): value is Message[] {\n return Array.isArray(value) && value.every(item => isMessage(item));\n}\n\n/**\n * Converts a `{ name, description, parameters }` object to the OpenAI tool format.\n * Objects already in the OpenAI format are returned unchanged.\n */\nfunction toApiTool(tool: unknown): ChatTool {\n const raw = isRecord(tool) ? tool : {};\n const definition = raw['function'];\n if (raw['type'] === 'function' && isRecord(definition)) {\n const { name, description, parameters, strict } = definition;\n return {\n type: 'function',\n function: {\n name: typeof name === 'string' ? name : '',\n ...(typeof description === 'string' ? { description } : {}),\n ...(isRecord(parameters) ? { parameters } : {}),\n ...(typeof strict === 'boolean' ? { strict } : {}),\n },\n };\n }\n const { name, description, parameters } = raw;\n return {\n type: 'function',\n function: {\n name: typeof name === 'string' ? name : '',\n description: typeof description === 'string' ? description : '',\n parameters: isRecord(parameters) ? parameters : {},\n },\n };\n}\n\n/**\n * Parses tool-call arguments. Anything that is not a JSON object becomes `{}`.\n */\nfunction parseArguments(raw: string): Record<string, unknown> {\n if (raw.trim().length === 0) {\n return {};\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n return isRecord(parsed) ? parsed : {};\n } catch {\n return {};\n }\n}\n\nfunction mapUsage(usage: ApiUsage): TokenUsage {\n return {\n completion_tokens: usage.completion_tokens ?? 0,\n prompt_tokens: usage.prompt_tokens ?? 0,\n total_tokens: usage.total_tokens ?? 0,\n ...(usage.prompt_cache_hit_tokens === undefined\n ? {}\n : { prompt_cache_hit_tokens: usage.prompt_cache_hit_tokens }),\n ...(usage.prompt_cache_miss_tokens === undefined\n ? {}\n : { prompt_cache_miss_tokens: usage.prompt_cache_miss_tokens }),\n };\n}\n\n/**\n * Model provider for any API that speaks the OpenAI chat completions protocol:\n * OpenAI itself, and the compatible endpoints of DeepSeek, Qwen, Moonshot, Ollama,\n * vLLM and others. Set `baseURL` and `model` for the provider you use.\n *\n * Ports:\n * - `prompt` (in): a text prompt. The answer is sent on `completion`.\n * - `messages` (in): a list of chat messages. The reply is sent on `message`.\n */\nclass OpenAICompatible extends Component {\n private readonly client: OpenAI;\n private readonly model: string;\n private readonly temperature: number;\n private readonly maxTokens: number | undefined;\n private readonly topP: number | undefined;\n private readonly systemPrompt: string | undefined;\n private readonly tools: unknown[] | undefined;\n private readonly verbose: boolean;\n private readonly name: string;\n\n constructor(config: OpenAICompatibleConfig) {\n super({});\n\n this.client = new OpenAI({\n apiKey: config.apiKey,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n dangerouslyAllowBrowser: config.dangerouslyAllowBrowser ?? false,\n });\n\n this.model = config.model;\n this.name = config.name ?? DEFAULT_NAME;\n this.temperature = config.temperature ?? DEFAULT_TEMPERATURE;\n this.maxTokens = config.maxTokens;\n this.topP = config.topP;\n this.systemPrompt = config.systemPrompt;\n this.verbose = config.verbose ?? false;\n this.tools =\n config.rawTools !== undefined && config.rawTools.length > 0\n ? config.rawTools.map(toApiTool)\n : config.tools;\n\n Component.Port.I('prompt').attach(this);\n Component.Port.I('messages').attach(this);\n Component.Port.O('completion').attach(this);\n Component.Port.O('message').attach(this);\n }\n\n /**\n * Generates a completion for a single prompt.\n *\n * @returns The text answer, or the JSON-encoded assistant message when it contains tool calls.\n */\n async generateCompletion(prompt: string): Promise<string> {\n const response = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: [...this.systemMessages(), { role: 'user', content: prompt }],\n ...this.toolsParam(this.tools),\n });\n\n const message = this.firstMessage(response);\n if (message.tool_calls !== undefined && message.tool_calls.length > 0) {\n return JSON.stringify(message);\n }\n return message.content ?? '';\n }\n\n /**\n * Sends a conversation and returns the assistant reply.\n */\n async chatCompletion(\n messages: Message[],\n options?: ChatCompletionOptions\n ): Promise<AssistantMessage> {\n const response = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: this.formatMessages(messages),\n ...this.toolsParam(options?.temporaryTools ?? this.tools),\n });\n\n const message = this.firstMessage(response);\n const result: AssistantMessage = { role: 'assistant', content: message.content ?? '' };\n\n if (response.usage !== undefined) {\n result.usage = mapUsage(response.usage);\n }\n\n if (message.tool_calls !== undefined && message.tool_calls.length > 0) {\n result.tool_calls = message.tool_calls.map(toolCall => {\n if (toolCall.type !== 'function') {\n throw new Error(`Unsupported tool call type: ${toolCall.type}`);\n }\n const rawArguments = toolCall.function.arguments;\n return {\n id: toolCall.id,\n type: 'function',\n function: {\n name: toolCall.function.name,\n arguments: rawArguments.length > 0 ? rawArguments : '{}',\n },\n tool_name: toolCall.function.name,\n arguments: parseArguments(rawArguments),\n };\n });\n }\n\n return result;\n }\n\n /**\n * Streams the assistant reply. Each chunk holds a text delta, tool-call deltas, or usage.\n */\n async *streamChatCompletion(\n messages: Message[],\n options?: ChatCompletionOptions\n ): AsyncGenerator<StreamChunk> {\n const stream = await this.client.chat.completions.create({\n ...this.baseRequest(),\n messages: this.formatMessages(messages),\n ...this.toolsParam(options?.temporaryTools ?? this.tools),\n stream: true,\n });\n\n for await (const chunk of stream) {\n const delta = chunk.choices[0]?.delta;\n\n const text = delta?.content ?? '';\n if (text !== '') {\n yield { role: 'assistant', content: text };\n }\n\n if (delta?.tool_calls) {\n yield {\n role: 'assistant',\n content: '',\n tool_calls: delta.tool_calls.map(toolCall => {\n const name = toolCall.function?.name ?? '';\n const args = toolCall.function?.arguments ?? '';\n return {\n id: toolCall.id ?? '',\n type: 'function',\n function: { name, arguments: args },\n tool_name: name,\n arguments: args,\n };\n }),\n };\n }\n\n if (chunk.usage) {\n yield { role: 'assistant', content: '', usage: mapUsage(chunk.usage) };\n }\n }\n }\n\n /**\n * Runs the model outside a pipeline.\n *\n * @param input - A prompt string or a list of messages.\n */\n override run(input: string | Message[]): Promise<string | AssistantMessage> {\n return typeof input === 'string' ? this.generateCompletion(input) : this.chatCompletion(input);\n }\n\n /**\n * Connects the `prompt` and `messages` input ports to the model.\n * API errors produce a fallback value so the pipeline keeps flowing. Input of the wrong\n * type fails the pipeline run with a `TypeError` before the API is called.\n */\n override _transform($i: Port, $o: Port): void {\n $i('prompt').receive(prompt => {\n if (typeof prompt !== 'string') {\n throw new TypeError(`${this.name}: the prompt port expects a string.`);\n }\n return this.generateCompletion(prompt).then(\n completion => {\n $o('completion').send(completion);\n },\n (error: unknown) => {\n this.logError(error);\n $o('completion').send(FALLBACK_TEXT);\n }\n );\n });\n\n $i('messages').receive(messages => {\n if (!isMessageList(messages)) {\n throw new TypeError(`${this.name}: the messages port expects an array of messages.`);\n }\n return this.chatCompletion(messages).then(\n message => {\n $o('message').send(message);\n },\n (error: unknown) => {\n this.logError(error);\n $o('message').send({ role: 'assistant', content: FALLBACK_TEXT });\n }\n );\n });\n }\n\n private baseRequest(): {\n model: string;\n temperature: number;\n max_tokens?: number;\n top_p?: number;\n } {\n return {\n model: this.model,\n temperature: this.temperature,\n ...(this.maxTokens === undefined ? {} : { max_tokens: this.maxTokens }),\n ...(this.topP === undefined ? {} : { top_p: this.topP }),\n };\n }\n\n private toolsParam(tools: unknown[] | undefined): { tools?: ChatTool[] } {\n return tools !== undefined && tools.length > 0 ? { tools: tools.map(toApiTool) } : {};\n }\n\n private systemMessages(): ChatMessageParam[] {\n return this.systemPrompt === undefined || this.systemPrompt === ''\n ? []\n : [{ role: 'system', content: this.systemPrompt }];\n }\n\n /**\n * Converts input messages to API messages. Assistant `tool_calls` are not forwarded:\n * agents send tool results back as user messages, and the API rejects assistant tool\n * calls that are not followed by tool messages.\n */\n private formatMessages(messages: Message[]): ChatMessageParam[] {\n return [...this.systemMessages(), ...messages.map(message => this.toApiMessage(message))];\n }\n\n /**\n * Converts one input message to its API shape.\n *\n * @throws Error when the role is not one of `MessageRole`, or a `tool` message has no\n * `tool_call_id`. The API would reject both.\n */\n private toApiMessage(message: Message): ChatMessageParam {\n const content = message.content ?? '';\n switch (message.role) {\n case 'system':\n return { role: 'system', content };\n case 'user':\n return { role: 'user', content };\n case 'assistant':\n return message.content === undefined\n ? { role: 'assistant' }\n : { role: 'assistant', content: message.content };\n case 'tool':\n if (message.tool_call_id === undefined || message.tool_call_id === '') {\n throw new Error(\n `${this.name}: a tool message needs the tool_call_id of the call it answers.`\n );\n }\n return { role: 'tool', content, tool_call_id: message.tool_call_id };\n default:\n throw new Error(\n `${this.name}: unsupported message role '${message.role}'. Use system, user, assistant or tool.`\n );\n }\n }\n\n private firstMessage(response: OpenAI.Chat.ChatCompletion): OpenAI.Chat.ChatCompletionMessage {\n const choice = response.choices[0];\n if (choice === undefined) {\n throw new Error(`${this.name} returned no choices.`);\n }\n return choice.message;\n }\n\n private logError(error: unknown): void {\n if (this.verbose) {\n // eslint-disable-next-line no-console -- opt-in diagnostics controlled by `verbose`.\n console.error(`[${this.name}] API call failed:`, error);\n }\n }\n}\n\nexport default OpenAICompatible;\n","import OpenAICompatible from '../openai-compatible';\nimport type { OpenAICompatibleConfig } from '../openai-compatible';\n\nexport type {\n AssistantMessage,\n ChatCompletionOptions,\n Message,\n MessageRole,\n RawTool,\n ResponseToolCall,\n StreamChunk,\n StreamToolCall,\n TokenUsage,\n ToolCall,\n} from '../openai-compatible';\n\nconst DEFAULT_BASE_URL = 'https://api.deepseek.com/v1';\nconst DEFAULT_MODEL = 'deepseek-chat';\n\n/**\n * Options for the DeepSeek model provider. They are the options of\n * `OpenAICompatible`, with DeepSeek's endpoint and model as defaults.\n */\nexport interface DeepseekConfig extends Omit<OpenAICompatibleConfig, 'model' | 'name'> {\n /** Model name. Defaults to `deepseek-chat`. */\n model?: string | undefined;\n /** API base URL. Defaults to `https://api.deepseek.com/v1`. */\n baseURL?: string | undefined;\n}\n\n/**\n * DeepSeek model provider: `OpenAICompatible` set up for the DeepSeek API.\n *\n * Ports:\n * - `prompt` (in): a text prompt. The answer is sent on `completion`.\n * - `messages` (in): a list of chat messages. The reply is sent on `message`.\n */\nclass Deepseek extends OpenAICompatible {\n constructor(config: DeepseekConfig) {\n super({\n ...config,\n model: config.model ?? DEFAULT_MODEL,\n baseURL: config.baseURL ?? DEFAULT_BASE_URL,\n name: 'DeepSeek',\n });\n }\n}\n\nexport default Deepseek;\n"],"mappings":"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","names":["name","description","parameters","DEFAULT_BASE_URL"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astack-tech/integrations",
3
- "version": "0.2.0-beta.10",
3
+ "version": "0.2.0-beta.12",
4
4
  "description": "Model provider integrations for AStack, starting with DeepSeek through the OpenAI-compatible API.",
5
5
  "keywords": [
6
6
  "astack",
@@ -59,7 +59,7 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "openai": "^6.10.0",
62
- "@astack-tech/core": "0.2.0-beta.4"
62
+ "@astack-tech/core": "0.2.0-beta.5"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@types/node": "^22.20.5",
@@ -196,6 +196,41 @@ function isRecord(value: unknown): value is Record<string, unknown> {
196
196
  return typeof value === 'object' && value !== null && !Array.isArray(value);
197
197
  }
198
198
 
199
+ function isOptionalString(value: unknown): value is string | undefined {
200
+ return value === undefined || typeof value === 'string';
201
+ }
202
+
203
+ function isCount(value: unknown): value is number {
204
+ return typeof value === 'number' && Number.isFinite(value);
205
+ }
206
+
207
+ function isTokenUsage(value: unknown): value is TokenUsage {
208
+ return (
209
+ isRecord(value) &&
210
+ isCount(value['completion_tokens']) &&
211
+ isCount(value['prompt_tokens']) &&
212
+ isCount(value['total_tokens']) &&
213
+ (value['prompt_cache_hit_tokens'] === undefined || isCount(value['prompt_cache_hit_tokens'])) &&
214
+ (value['prompt_cache_miss_tokens'] === undefined || isCount(value['prompt_cache_miss_tokens']))
215
+ );
216
+ }
217
+
218
+ /** Whether `value` is a `Message`, checked field by field. The role is checked when the message is sent. */
219
+ function isMessage(value: unknown): value is Message {
220
+ return (
221
+ isRecord(value) &&
222
+ typeof value['role'] === 'string' &&
223
+ isOptionalString(value['content']) &&
224
+ (value['tool_calls'] === undefined || Array.isArray(value['tool_calls'])) &&
225
+ isOptionalString(value['tool_call_id']) &&
226
+ (value['usage'] === undefined || isTokenUsage(value['usage']))
227
+ );
228
+ }
229
+
230
+ function isMessageList(value: unknown): value is Message[] {
231
+ return Array.isArray(value) && value.every(item => isMessage(item));
232
+ }
233
+
199
234
  /**
200
235
  * Converts a `{ name, description, parameters }` object to the OpenAI tool format.
201
236
  * Objects already in the OpenAI format are returned unchanged.
@@ -420,11 +455,15 @@ class OpenAICompatible extends Component {
420
455
 
421
456
  /**
422
457
  * Connects the `prompt` and `messages` input ports to the model.
423
- * API errors produce a fallback value so the pipeline keeps flowing.
458
+ * API errors produce a fallback value so the pipeline keeps flowing. Input of the wrong
459
+ * type fails the pipeline run with a `TypeError` before the API is called.
424
460
  */
425
461
  override _transform($i: Port, $o: Port): void {
426
- $i('prompt').receive((prompt: string) => {
427
- this.generateCompletion(prompt).then(
462
+ $i('prompt').receive(prompt => {
463
+ if (typeof prompt !== 'string') {
464
+ throw new TypeError(`${this.name}: the prompt port expects a string.`);
465
+ }
466
+ return this.generateCompletion(prompt).then(
428
467
  completion => {
429
468
  $o('completion').send(completion);
430
469
  },
@@ -435,8 +474,11 @@ class OpenAICompatible extends Component {
435
474
  );
436
475
  });
437
476
 
438
- $i('messages').receive((messages: Message[]) => {
439
- this.chatCompletion(messages).then(
477
+ $i('messages').receive(messages => {
478
+ if (!isMessageList(messages)) {
479
+ throw new TypeError(`${this.name}: the messages port expects an array of messages.`);
480
+ }
481
+ return this.chatCompletion(messages).then(
440
482
  message => {
441
483
  $o('message').send(message);
442
484
  },