@yeaft/webchat-agent 0.1.416 → 0.1.418

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.416",
3
+ "version": "0.1.418",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -12,6 +12,12 @@
12
12
  * Response: delta.tool_calls[i].function.arguments (JSON string) → accumulate → JSON.parse → UnifiedToolCall
13
13
  * Result: UnifiedToolResult → { role: "tool", tool_call_id, content }
14
14
  * Finish: "tool_calls" → "tool_use", "stop" → "end_turn", "length" → "max_tokens"
15
+ *
16
+ * max_tokens strategy (based on model ID):
17
+ * OpenAI models (gpt-*, o1*, o3*, o4*) use "max_completion_tokens" (new standard).
18
+ * All other models (DeepSeek, Gemini, etc.) use "max_tokens" (legacy/compat).
19
+ * CopilotProxy transparently forwards whatever the client sends.
20
+ * Callers can override via extraBody to pass any parameter directly.
15
21
  */
16
22
 
17
23
  import {
@@ -23,6 +29,26 @@ import {
23
29
  LLMAbortError,
24
30
  } from './adapter.js';
25
31
 
32
+ /**
33
+ * Check if a model ID is an OpenAI model that supports max_completion_tokens.
34
+ * OpenAI introduced max_completion_tokens with o1 and made it standard for
35
+ * GPT-4.1+, o-series, and GPT-5+. Other OpenAI-compatible APIs (DeepSeek,
36
+ * Gemini, Ollama) still only understand max_tokens.
37
+ *
38
+ * @param {string} model — The model ID (e.g. "gpt-5", "deepseek-chat", "o3")
39
+ * @returns {boolean} true = use max_completion_tokens
40
+ */
41
+ export function useNewMaxTokensParam(model) {
42
+ if (!model) return false;
43
+ const m = model.toLowerCase();
44
+ // GPT-4.1+ and GPT-5+
45
+ if (m.startsWith('gpt-')) return true;
46
+ // o-series reasoning models (o1, o3, o4-mini, etc.)
47
+ if (/^o\d/.test(m)) return true;
48
+ // Everything else (deepseek-*, gemini-*, claude-*, custom models): legacy
49
+ return false;
50
+ }
51
+
26
52
  /**
27
53
  * ChatCompletionsAdapter — Talks to OpenAI Chat Completions API and compatibles.
28
54
  */
@@ -39,6 +65,24 @@ export class ChatCompletionsAdapter extends LLMAdapter {
39
65
  this.#baseUrl = baseUrl.replace(/\/+$/, ''); // strip trailing slash
40
66
  }
41
67
 
68
+ /** Expose baseUrl for testing. */
69
+ get baseUrl() { return this.#baseUrl; }
70
+
71
+ /**
72
+ * Build the max-tokens portion of the request body.
73
+ * Uses max_completion_tokens for OpenAI models, max_tokens for others.
74
+ *
75
+ * @param {string} model
76
+ * @param {number} maxTokens
77
+ * @returns {object}
78
+ */
79
+ #maxTokensBody(model, maxTokens) {
80
+ if (useNewMaxTokensParam(model)) {
81
+ return { max_completion_tokens: maxTokens };
82
+ }
83
+ return { max_tokens: maxTokens };
84
+ }
85
+
42
86
  /**
43
87
  * Translate UnifiedToolDef[] → Chat Completions tool format.
44
88
  * @param {import('./adapter.js').UnifiedToolDef[]} tools
@@ -140,16 +184,16 @@ export class ChatCompletionsAdapter extends LLMAdapter {
140
184
  }
141
185
 
142
186
  /**
143
- * @param {{ model: string, system: string, messages: import('./adapter.js').UnifiedMessage[], tools?: import('./adapter.js').UnifiedToolDef[], maxTokens?: number, signal?: AbortSignal }} params
187
+ * @param {{ model: string, system: string, messages: import('./adapter.js').UnifiedMessage[], tools?: import('./adapter.js').UnifiedToolDef[], maxTokens?: number, extraBody?: object, signal?: AbortSignal }} params
144
188
  * @returns {AsyncGenerator<import('./adapter.js').StreamEvent>}
145
189
  */
146
- async *stream({ model, system, messages, tools, maxTokens = 16384, signal }) {
190
+ async *stream({ model, system, messages, tools, maxTokens = 16384, extraBody, signal }) {
147
191
  if (signal?.aborted) throw new LLMAbortError();
148
192
 
149
193
  const body = {
150
194
  model,
151
195
  messages: this.#translateMessages(system, messages),
152
- max_tokens: maxTokens,
196
+ ...this.#maxTokensBody(model, maxTokens),
153
197
  stream: true,
154
198
  stream_options: { include_usage: true },
155
199
  };
@@ -157,6 +201,9 @@ export class ChatCompletionsAdapter extends LLMAdapter {
157
201
  const translatedTools = this.#translateTools(tools);
158
202
  if (translatedTools) body.tools = translatedTools;
159
203
 
204
+ // extraBody allows callers to pass through any additional/override parameters
205
+ if (extraBody) Object.assign(body, extraBody);
206
+
160
207
  const response = await fetch(`${this.#baseUrl}/chat/completions`, {
161
208
  method: 'POST',
162
209
  headers: {
@@ -277,15 +324,18 @@ export class ChatCompletionsAdapter extends LLMAdapter {
277
324
  /**
278
325
  * Non-streaming call for side queries.
279
326
  */
280
- async call({ model, system, messages, maxTokens = 4096, signal }) {
327
+ async call({ model, system, messages, maxTokens = 4096, extraBody, signal }) {
281
328
  if (signal?.aborted) throw new LLMAbortError();
282
329
 
283
330
  const body = {
284
331
  model,
285
332
  messages: this.#translateMessages(system, messages),
286
- max_tokens: maxTokens,
333
+ ...this.#maxTokensBody(model, maxTokens),
287
334
  };
288
335
 
336
+ // extraBody allows callers to pass through any additional/override parameters
337
+ if (extraBody) Object.assign(body, extraBody);
338
+
289
339
  const response = await fetch(`${this.#baseUrl}/chat/completions`, {
290
340
  method: 'POST',
291
341
  headers: {