open-agents-ai 0.187.454 → 0.187.456

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/dist/index.js CHANGED
@@ -575872,7 +575872,6 @@ const statusEl = document.getElementById('status');
575872
575872
  let streaming = false;
575873
575873
  let messages = [];
575874
575874
  let chatAbortController = null; // for stop button
575875
- let chatAbortController = null; // for stop button
575876
575875
  // WO-CHAT-RESUME — interval id for the in-flight job poller. Set when we
575877
575876
  // detect an active job on reload; cleared when the job reaches a terminal
575878
575877
  // state OR the user navigates away.
@@ -580086,6 +580085,137 @@ function getSwaggerUI() {
580086
580085
  </body>
580087
580086
  </html>`;
580088
580087
  }
580088
+ function getRedocHTML() {
580089
+ return `<!DOCTYPE html>
580090
+ <html lang="en">
580091
+ <head>
580092
+ <meta charset="UTF-8">
580093
+ <title>Open Agents — API Reference (ReDoc)</title>
580094
+ <meta name="viewport" content="width=device-width, initial-scale=1">
580095
+ <link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
580096
+ <style>
580097
+ body { margin: 0; padding: 0; }
580098
+ #oa-banner { padding: 8px 14px; background: #1e1e22; color: #b0b0b0; font: 12px ui-monospace, monospace; border-bottom: 1px solid #2a2a30; }
580099
+ #oa-banner a { color: #b2920a; text-decoration: none; margin-right: 14px; }
580100
+ #oa-banner b { color: #b2920a; }
580101
+ </style>
580102
+ </head>
580103
+ <body>
580104
+ <div id="oa-banner">
580105
+ <b>Open Agents — ReDoc view</b>
580106
+ <a href="/api/docs">Swagger UI (interactive)</a>
580107
+ <a href="/openapi.json">openapi.json</a>
580108
+ <a href="/openapi.yaml">openapi.yaml</a>
580109
+ <a href="/asyncapi.json">asyncapi.json</a>
580110
+ <a href="/v1/routes">routes summary</a>
580111
+ <a href="/">root</a>
580112
+ </div>
580113
+ <redoc spec-url="/openapi.json"></redoc>
580114
+ <script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
580115
+ </body>
580116
+ </html>`;
580117
+ }
580118
+ function getAsyncApiSpec() {
580119
+ const oa = getOpenApiSpec();
580120
+ const clientSchema = oa.components?.schemas?.VoicechatClientFrame;
580121
+ const serverSchema = oa.components?.schemas?.VoicechatServerFrame;
580122
+ return {
580123
+ asyncapi: "2.6.0",
580124
+ info: {
580125
+ title: "Open Agents — Voicechat (WebSocket)",
580126
+ version: "1.0.0",
580127
+ description: "Full-duplex voicechat over WebSocket. Binary frames carry PCM audio (mic in 16 kHz mono Int16 LE; TTS out at the model's native sample rate, announced in the preceding `tts_header` text frame). Text frames carry JSON control messages.",
580128
+ license: { name: "CC-BY-NC-4.0", url: "https://creativecommons.org/licenses/by-nc/4.0/" }
580129
+ },
580130
+ servers: {
580131
+ local: {
580132
+ url: "ws://localhost:11435",
580133
+ protocol: "ws",
580134
+ description: "Daemon on localhost. The same path works on LAN IP, public-IP forwarding, or behind a TLS-terminating proxy (then `wss://`)."
580135
+ }
580136
+ },
580137
+ defaultContentType: "application/json",
580138
+ channels: {
580139
+ "/v1/voicechat/ws": {
580140
+ description: "Voicechat full-duplex channel. Open with `?user=<displayName>` to set the connected-user name.",
580141
+ parameters: {
580142
+ user: { description: "Optional display name (default: 'browser').", schema: { type: "string" } }
580143
+ },
580144
+ publish: {
580145
+ summary: "Frames the client sends to the server (binary mic PCM + JSON control).",
580146
+ operationId: "publishToVoicechat",
580147
+ message: {
580148
+ oneOf: [
580149
+ { name: "MicAudio", title: "Microphone PCM", contentType: "application/octet-stream", summary: "Binary frame: PCM Int16 mono 16 kHz from the user's mic (typically 4096 samples per frame).", payload: { type: "string", format: "binary" } },
580150
+ { name: "ClientControl", title: "JSON control", payload: { $ref: "#/components/schemas/VoicechatClientFrame" } }
580151
+ ]
580152
+ }
580153
+ },
580154
+ subscribe: {
580155
+ summary: "Frames the server sends to the client (binary TTS PCM + JSON control).",
580156
+ operationId: "subscribeToVoicechat",
580157
+ message: {
580158
+ oneOf: [
580159
+ { name: "TtsAudio", title: "TTS PCM", contentType: "application/octet-stream", summary: "Binary frame: PCM Int16 mono at the model's native sample rate. ALWAYS preceded by a `tts_header` JSON frame announcing `sampleRate`.", payload: { type: "string", format: "binary" } },
580160
+ { name: "ServerControl", title: "JSON control", payload: { $ref: "#/components/schemas/VoicechatServerFrame" } }
580161
+ ]
580162
+ }
580163
+ }
580164
+ }
580165
+ },
580166
+ components: {
580167
+ schemas: {
580168
+ VoicechatClientFrame: clientSchema,
580169
+ VoicechatServerFrame: serverSchema
580170
+ }
580171
+ }
580172
+ };
580173
+ }
580174
+ function jsonToYaml(value2, indent = 0) {
580175
+ const pad = (n2) => " ".repeat(n2);
580176
+ if (value2 === null) return "null";
580177
+ if (value2 === void 0) return "null";
580178
+ if (typeof value2 === "boolean") return value2 ? "true" : "false";
580179
+ if (typeof value2 === "number") return Number.isFinite(value2) ? String(value2) : "null";
580180
+ if (typeof value2 === "string") {
580181
+ if (value2.length === 0) return '""';
580182
+ if (/[\n\r]/.test(value2)) {
580183
+ const lines = value2.replace(/\r\n?/g, "\n").split("\n");
580184
+ return "|-\n" + lines.map((l2) => pad(indent + 1) + l2).join("\n");
580185
+ }
580186
+ if (/^(true|false|null|yes|no|on|off|~|\d|-\d|\.\d|\.inf|\.nan|@|`|\*|&|!|\?|\||>|%|"|')/i.test(value2) || /[#:,\[\]{}]/.test(value2) || /^\s|\s$/.test(value2)) {
580187
+ return JSON.stringify(value2);
580188
+ }
580189
+ return value2;
580190
+ }
580191
+ if (Array.isArray(value2)) {
580192
+ if (value2.length === 0) return "[]";
580193
+ return "\n" + value2.map((item) => {
580194
+ const rendered = jsonToYaml(item, indent + 1);
580195
+ if (rendered.startsWith("\n")) {
580196
+ return pad(indent) + "-" + rendered.replace(/^\n/, "\n");
580197
+ }
580198
+ const lines = rendered.split("\n");
580199
+ if (lines.length === 1) return pad(indent) + "- " + lines[0];
580200
+ return pad(indent) + "- " + lines[0] + "\n" + lines.slice(1).map((l2) => pad(indent + 1) + l2.replace(/^ /, "")).join("\n");
580201
+ }).join("\n");
580202
+ }
580203
+ if (typeof value2 === "object") {
580204
+ const obj = value2;
580205
+ const keys = Object.keys(obj);
580206
+ if (keys.length === 0) return "{}";
580207
+ return "\n" + keys.map((k) => {
580208
+ const v = obj[k];
580209
+ const safeKey = /^[a-zA-Z_][a-zA-Z0-9_./-]*$/.test(k) && !/^(true|false|null|yes|no|on|off)$/i.test(k) ? k : JSON.stringify(k);
580210
+ const rendered = jsonToYaml(v, indent + 1);
580211
+ if (rendered.startsWith("\n")) {
580212
+ return pad(indent) + safeKey + ":" + rendered;
580213
+ }
580214
+ return pad(indent) + safeKey + ": " + rendered;
580215
+ }).join("\n");
580216
+ }
580217
+ return JSON.stringify(value2);
580218
+ }
580089
580219
  var SWAGGER_VERSION;
580090
580220
  var init_openapi = __esm({
580091
580221
  "packages/cli/src/api/openapi.ts"() {
@@ -581217,14 +581347,14 @@ function sanitizeChatContent(raw) {
581217
581347
  return cleaned.join("\n").trim();
581218
581348
  }
581219
581349
  async function directChatBackend(opts) {
581220
- const { model, messages: messages2, stream, res, sessionId, ollamaUrl } = opts;
581350
+ const { model, messages: messages2, stream, res, sessionId, ollamaUrl, extraFields } = opts;
581221
581351
  const cfg = loadConfig();
581222
581352
  const isVllm = cfg.backendType === "vllm";
581223
581353
  const cleanModel = model.replace(/^[a-z]+\//, "");
581224
581354
  const headers = { "Content-Type": "application/json" };
581225
581355
  if (cfg.apiKey) headers["Authorization"] = `Bearer ${cfg.apiKey}`;
581226
581356
  if (isVllm) {
581227
- const reqBody = JSON.stringify({ model: cleanModel, messages: messages2, stream });
581357
+ const reqBody = JSON.stringify({ model: cleanModel, messages: messages2, stream, ...extraFields || {} });
581228
581358
  if (stream) {
581229
581359
  res.writeHead(200, {
581230
581360
  "Content-Type": "text/event-stream",
@@ -581292,12 +581422,27 @@ async function directChatBackend(opts) {
581292
581422
  return content;
581293
581423
  }
581294
581424
  } else {
581425
+ const ef = extraFields || {};
581426
+ const ollamaOpts = {};
581427
+ if (typeof ef["temperature"] === "number") ollamaOpts["temperature"] = ef["temperature"];
581428
+ if (typeof ef["top_p"] === "number") ollamaOpts["top_p"] = ef["top_p"];
581429
+ if (typeof ef["max_tokens"] === "number") ollamaOpts["num_predict"] = ef["max_tokens"];
581430
+ if (typeof ef["seed"] === "number") ollamaOpts["seed"] = ef["seed"];
581431
+ if (typeof ef["frequency_penalty"] === "number") ollamaOpts["frequency_penalty"] = ef["frequency_penalty"];
581432
+ if (typeof ef["presence_penalty"] === "number") ollamaOpts["presence_penalty"] = ef["presence_penalty"];
581433
+ if (Array.isArray(ef["stop"]) || typeof ef["stop"] === "string") ollamaOpts["stop"] = ef["stop"];
581434
+ const hasTools = Array.isArray(ef["tools"]) && ef["tools"].length > 0;
581295
581435
  const reqBody = JSON.stringify({
581296
581436
  model: cleanModel,
581297
581437
  messages: messages2,
581298
581438
  stream,
581299
- think: false,
581300
- options: {}
581439
+ // Don't force think:false when the caller is using tool calling —
581440
+ // thinking models often need their reasoning chain to choose a tool.
581441
+ ...hasTools ? {} : { think: false },
581442
+ ...hasTools ? { tools: ef["tools"] } : {},
581443
+ ...ef["tool_choice"] !== void 0 ? { tool_choice: ef["tool_choice"] } : {},
581444
+ ...ef["response_format"] !== void 0 ? { format: ef["response_format"] } : {},
581445
+ options: ollamaOpts
581301
581446
  });
581302
581447
  if (stream) {
581303
581448
  res.writeHead(200, {
@@ -581353,7 +581498,20 @@ async function directChatBackend(opts) {
581353
581498
  if (result.status >= 400) throw new Error(`Backend HTTP ${result.status}: ${result.body.slice(0, 200)}`);
581354
581499
  const j = JSON.parse(result.body);
581355
581500
  const content = j?.message?.content || "";
581356
- if (!content) throw new Error("Backend returned empty message content");
581501
+ const rawToolCalls = j?.message?.tool_calls;
581502
+ const toolCalls = Array.isArray(rawToolCalls) ? rawToolCalls.map((tc, idx) => ({
581503
+ id: tc.id || `call_${Math.random().toString(36).slice(2, 10)}`,
581504
+ type: "function",
581505
+ function: {
581506
+ name: tc?.function?.name ?? tc?.name ?? "",
581507
+ arguments: typeof tc?.function?.arguments === "string" ? tc.function.arguments : JSON.stringify(tc?.function?.arguments ?? tc?.arguments ?? {})
581508
+ },
581509
+ index: idx
581510
+ })) : [];
581511
+ if (!content && toolCalls.length === 0) {
581512
+ throw new Error("Backend returned empty message (no content, no tool_calls)");
581513
+ }
581514
+ const finishReason = toolCalls.length > 0 ? "tool_calls" : "stop";
581357
581515
  const promptTokens = j?.prompt_eval_count ?? 0;
581358
581516
  const completionTokens = j?.eval_count ?? 0;
581359
581517
  jsonResponse(res, 200, {
@@ -581363,8 +581521,12 @@ async function directChatBackend(opts) {
581363
581521
  model: cleanModel,
581364
581522
  choices: [{
581365
581523
  index: 0,
581366
- message: { role: "assistant", content },
581367
- finish_reason: "stop"
581524
+ message: {
581525
+ role: "assistant",
581526
+ content,
581527
+ ...toolCalls.length > 0 ? { tool_calls: toolCalls } : {}
581528
+ },
581529
+ finish_reason: finishReason
581368
581530
  }],
581369
581531
  usage: {
581370
581532
  prompt_tokens: promptTokens,
@@ -582008,10 +582170,13 @@ async function handleV1ChatCompletions(req2, res, ollamaUrl) {
582008
582170
  }
582009
582171
  return;
582010
582172
  }
582173
+ const callerProvidedThink = "think" in routedBody;
582174
+ const callerProvidedTools = Array.isArray(routedBody["tools"]) && routedBody["tools"].length > 0;
582175
+ const finalThink = callerProvidedThink ? routedBody["think"] : callerProvidedTools ? void 0 : false;
582011
582176
  const ollamaPayload = JSON.stringify({
582012
582177
  ...routedBody,
582013
582178
  stream,
582014
- think: false
582179
+ ...finalThink !== void 0 ? { think: finalThink } : {}
582015
582180
  });
582016
582181
  if (stream) {
582017
582182
  res.writeHead(200, {
@@ -582059,7 +582224,17 @@ async function handleV1ChatCompletions(req2, res, ollamaUrl) {
582059
582224
  const delta = {};
582060
582225
  if (ollamaChunk.message.role) delta.role = ollamaChunk.message.role;
582061
582226
  if (ollamaChunk.message.content) delta.content = ollamaChunk.message.content;
582062
- if (ollamaChunk.message.tool_calls) delta.tool_calls = ollamaChunk.message.tool_calls;
582227
+ if (ollamaChunk.message.tool_calls) {
582228
+ delta.tool_calls = ollamaChunk.message.tool_calls.map((tc, idx) => ({
582229
+ id: tc.id || `call_${randomBytes21(8).toString("hex")}`,
582230
+ type: "function",
582231
+ function: {
582232
+ name: tc?.function?.name ?? tc?.name ?? "",
582233
+ arguments: typeof tc?.function?.arguments === "string" ? tc.function.arguments : JSON.stringify(tc?.function?.arguments ?? tc?.arguments ?? {})
582234
+ },
582235
+ index: idx
582236
+ }));
582237
+ }
582063
582238
  const sseEvent = {
582064
582239
  id: chatId,
582065
582240
  object: "chat.completion.chunk",
@@ -582135,7 +582310,15 @@ async function handleV1ChatCompletions(req2, res, ollamaUrl) {
582135
582310
  content: ollamaResp.message?.content ?? ""
582136
582311
  };
582137
582312
  if (ollamaResp.message?.tool_calls && ollamaResp.message.tool_calls.length > 0) {
582138
- responseMessage.tool_calls = ollamaResp.message.tool_calls;
582313
+ responseMessage.tool_calls = ollamaResp.message.tool_calls.map((tc, idx) => ({
582314
+ id: tc.id || `call_${randomBytes21(8).toString("hex")}`,
582315
+ type: "function",
582316
+ function: {
582317
+ name: tc?.function?.name ?? tc?.name ?? "",
582318
+ arguments: typeof tc?.function?.arguments === "string" ? tc.function.arguments : JSON.stringify(tc?.function?.arguments ?? tc?.arguments ?? {})
582319
+ },
582320
+ index: idx
582321
+ }));
582139
582322
  if (!ollamaResp.message.content) responseMessage.content = null;
582140
582323
  }
582141
582324
  const hasToolCalls = !!ollamaResp.message?.tool_calls?.length;
@@ -583584,6 +583767,103 @@ async function handleRequest(req2, res, ollamaUrl, verbose) {
583584
583767
  return;
583585
583768
  }
583586
583769
  }
583770
+ if ((pathname === "/v3/api-docs" || pathname === "/swagger.json" || pathname === "/api-docs") && method === "GET") {
583771
+ jsonResponse(res, 200, getOpenApiSpec());
583772
+ return;
583773
+ }
583774
+ if (pathname === "/openapi.yaml" && method === "GET") {
583775
+ const spec = getOpenApiSpec();
583776
+ const yaml = jsonToYaml(spec);
583777
+ res.writeHead(200, {
583778
+ "Content-Type": "application/yaml; charset=utf-8",
583779
+ "Cache-Control": "public, max-age=60"
583780
+ });
583781
+ res.end(yaml);
583782
+ return;
583783
+ }
583784
+ if ((pathname === "/swagger-ui" || pathname === "/swagger-ui/" || pathname === "/swagger-ui/index.html") && method === "GET") {
583785
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
583786
+ res.end(getSwaggerUI());
583787
+ return;
583788
+ }
583789
+ if ((pathname === "/redoc" || pathname === "/redoc/") && method === "GET") {
583790
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
583791
+ res.end(getRedocHTML());
583792
+ return;
583793
+ }
583794
+ if ((pathname === "/asyncapi.json" || pathname === "/asyncapi") && method === "GET") {
583795
+ jsonResponse(res, 200, getAsyncApiSpec());
583796
+ return;
583797
+ }
583798
+ if (pathname === "/" && method === "GET" && !req2.headers.accept?.includes("text/html")) {
583799
+ const cfg = loadConfig();
583800
+ const baseUrl = `${req2.headers["x-forwarded-proto"] || (req2.socket && req2.socket.encrypted ? "https" : "http")}://${req2.headers.host || "localhost"}`;
583801
+ jsonResponse(res, 200, {
583802
+ name: "open-agents",
583803
+ version: API_VERSION,
583804
+ description: "Open Agents AI — local agentic stack with OpenAI-compatible inference, voice (TTS/ASR/voice-clone), per-project preferences, AIMS controls, and full WebSocket voicechat. All endpoints documented in /openapi.json + interactive at /api/docs.",
583805
+ documentation_url: `${baseUrl}/api/docs`,
583806
+ openapi_spec_url: `${baseUrl}/openapi.json`,
583807
+ asyncapi_spec_url: `${baseUrl}/asyncapi.json`,
583808
+ backend: { url: cfg.backendUrl, type: cfg.backendType, model: cfg.model },
583809
+ // _links shape mirrors GitHub's API root + JSON-Home flavor.
583810
+ // Each cluster has a one-line description so an agent doesn't need
583811
+ // to read the OpenAPI spec to know what's there.
583812
+ _links: {
583813
+ self: `${baseUrl}/`,
583814
+ health: { href: `${baseUrl}/health`, description: "Liveness + version + uptime" },
583815
+ openapi: { href: `${baseUrl}/openapi.json`, description: "OpenAPI 3.0 spec (machine-readable). Aliases: /openapi.yaml, /v3/api-docs, /swagger.json, /api-docs." },
583816
+ asyncapi: { href: `${baseUrl}/asyncapi.json`, description: "AsyncAPI 2.6 spec for the /v1/voicechat/ws WebSocket." },
583817
+ docs: { href: `${baseUrl}/api/docs`, description: "Interactive Swagger UI. Aliases: /docs, /swagger-ui." },
583818
+ redoc: { href: `${baseUrl}/redoc`, description: "ReDoc renderer of the same /openapi.json." },
583819
+ routes: { href: `${baseUrl}/v1/routes`, description: "Flat grep-friendly summary of every route (~5 KB)." },
583820
+ chat: {
583821
+ completions: { href: `${baseUrl}/v1/chat/completions`, description: "OpenAI-compatible chat completion. Native tool calling supported." },
583822
+ session: { href: `${baseUrl}/v1/chat`, description: "Stateful chat session with optional agent subprocess (set tools=true for full tool stack)." },
583823
+ sessions: { href: `${baseUrl}/v1/chat/sessions`, description: "List active chat sessions." }
583824
+ },
583825
+ voice: {
583826
+ state: { href: `${baseUrl}/v1/voice/state`, description: "Voice runtime status." },
583827
+ tts_models: { href: `${baseUrl}/v1/voice/models`, description: "List TTS voice models." },
583828
+ tts: { href: `${baseUrl}/v1/voice/tts`, description: "Synthesize text → audio bytes (also at /v1/audio/speech for OpenAI compat)." },
583829
+ asr_models: { href: `${baseUrl}/v1/voice/asr-models`, description: "List Whisper ASR models." },
583830
+ transcribe: { href: `${baseUrl}/v1/voice/transcribe`, description: "Transcribe audio → text (also at /v1/audio/transcriptions)." },
583831
+ transcribe_stream: { href: `${baseUrl}/v1/voice/transcribe/stream`, description: "Streaming ASR via SSE." },
583832
+ voicechat_ws: { href: `${baseUrl}/v1/voicechat/ws`, description: "Full-duplex voicechat WebSocket. See /asyncapi.json for the frame schemas." },
583833
+ clone_refs: { href: `${baseUrl}/v1/voice/clone-refs`, description: "Voice clone reference management. Upload via JSON+base64, raw multipart, or from-URL." },
583834
+ speak: { href: `${baseUrl}/v1/voice/speak`, description: "Synthesize text and broadcast to /v1/voicechat/ws clients." }
583835
+ },
583836
+ projects: {
583837
+ list: { href: `${baseUrl}/v1/projects`, description: "Workspace registry." },
583838
+ current: { href: `${baseUrl}/v1/projects/current`, description: "Active project." },
583839
+ switch: { href: `${baseUrl}/v1/projects/switch`, description: "Set the active project (POST)." },
583840
+ preferences: { href: `${baseUrl}/v1/projects/preferences`, description: "Per-project UI preferences (model, theme, current chat session, etc.)." }
583841
+ },
583842
+ inference: {
583843
+ models: { href: `${baseUrl}/v1/models`, description: "OpenAI-compatible model list." },
583844
+ embeddings: { href: `${baseUrl}/v1/embeddings`, description: "Generate embeddings." }
583845
+ },
583846
+ agentic: {
583847
+ run: { href: `${baseUrl}/v1/run`, description: "Submit autonomous task." },
583848
+ runs: { href: `${baseUrl}/v1/runs`, description: "List runs." },
583849
+ tools: { href: `${baseUrl}/v1/tools`, description: "Agentic tool registry." },
583850
+ mcps: { href: `${baseUrl}/v1/mcps`, description: "MCP server registry." }
583851
+ },
583852
+ memory: {
583853
+ root: { href: `${baseUrl}/v1/memory`, description: "Memory backends summary." },
583854
+ search: { href: `${baseUrl}/v1/memory/search`, description: "Search memory." },
583855
+ episodes: { href: `${baseUrl}/v1/memory/episodes`, description: "List episodes." }
583856
+ },
583857
+ observability: {
583858
+ usage: { href: `${baseUrl}/v1/usage`, description: "Token usage + rate limits." },
583859
+ audit: { href: `${baseUrl}/v1/audit`, description: "Audit log." },
583860
+ events: { href: `${baseUrl}/v1/events`, description: "SSE event bus." }
583861
+ },
583862
+ aims: { href: `${baseUrl}/v1/aims`, description: "ISO/IEC 42001:2023 AIMS controls (policies, roles, resources, impact assessments, …)." }
583863
+ }
583864
+ });
583865
+ return;
583866
+ }
583587
583867
  if (pathname === "/" && method === "GET" && req2.headers.accept?.includes("text/html")) {
583588
583868
  res.writeHead(200, {
583589
583869
  "Content-Type": "text/html; charset=utf-8",
@@ -584601,19 +584881,42 @@ data: ${JSON.stringify(data)}
584601
584881
  const session = getSession(sessionId, model, cwdPath);
584602
584882
  addUserMessage(session, chatBody.message);
584603
584883
  compactSession(session);
584604
- const wantsTools = chatBody["tools"] !== false && chatBody["use_tools"] !== false;
584605
584884
  const streamMode = chatBody.stream !== false;
584606
- if (!wantsTools) {
584885
+ const toolsField = chatBody["tools"];
584886
+ const isOpenAIToolsArray = Array.isArray(toolsField) && toolsField.length > 0 && toolsField.every(
584887
+ (t2) => t2 && typeof t2 === "object" && t2["type"] === "function" && typeof t2["function"]?.["name"] === "string"
584888
+ );
584889
+ const wantsAgent = !isOpenAIToolsArray && chatBody["tools"] !== false && chatBody["use_tools"] !== false;
584890
+ if (Array.isArray(chatBody.messages)) {
584891
+ const incomingSystem = chatBody.messages.find((m2) => m2?.role === "system" && typeof m2.content === "string");
584892
+ if (incomingSystem && session.messages.length > 0 && session.messages[0].role === "system") {
584893
+ session.messages[0] = { ...session.messages[0], content: incomingSystem.content };
584894
+ }
584895
+ }
584896
+ if (!wantsAgent) {
584607
584897
  try {
584608
584898
  const ans = await directChatBackend({
584609
584899
  model,
584610
- // Filter out tool_call/tool_result messages — the upstream
584611
- // model only accepts standard system/user/assistant turns.
584612
584900
  messages: session.messages.filter((m2) => m2.role === "system" || m2.role === "user" || m2.role === "assistant").map((m2) => ({ role: m2.role, content: m2.content })),
584613
584901
  stream: streamMode,
584614
584902
  res,
584615
584903
  sessionId: session.id,
584616
- ollamaUrl
584904
+ ollamaUrl,
584905
+ // Pass-through of OpenAI fields — the upstream model needs
584906
+ // them for native tool calling, deterministic outputs, etc.
584907
+ extraFields: {
584908
+ ...isOpenAIToolsArray ? { tools: toolsField } : {},
584909
+ ...chatBody["tool_choice"] !== void 0 ? { tool_choice: chatBody["tool_choice"] } : {},
584910
+ ...chatBody["temperature"] !== void 0 ? { temperature: chatBody["temperature"] } : {},
584911
+ ...chatBody["top_p"] !== void 0 ? { top_p: chatBody["top_p"] } : {},
584912
+ ...chatBody["max_tokens"] !== void 0 ? { max_tokens: chatBody["max_tokens"] } : {},
584913
+ ...chatBody["response_format"] !== void 0 ? { response_format: chatBody["response_format"] } : {},
584914
+ ...chatBody["seed"] !== void 0 ? { seed: chatBody["seed"] } : {},
584915
+ ...chatBody["frequency_penalty"] !== void 0 ? { frequency_penalty: chatBody["frequency_penalty"] } : {},
584916
+ ...chatBody["presence_penalty"] !== void 0 ? { presence_penalty: chatBody["presence_penalty"] } : {},
584917
+ ...chatBody["stop"] !== void 0 ? { stop: chatBody["stop"] } : {},
584918
+ ...chatBody["parallel_tool_calls"] !== void 0 ? { parallel_tool_calls: chatBody["parallel_tool_calls"] } : {}
584919
+ }
584617
584920
  });
584618
584921
  if (ans !== null) {
584619
584922
  addAssistantMessage(session, ans);
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.454",
3
+ "version": "0.187.456",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "open-agents-ai",
9
- "version": "0.187.454",
9
+ "version": "0.187.456",
10
10
  "hasInstallScript": true,
11
11
  "license": "CC-BY-NC-4.0",
12
12
  "dependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.454",
3
+ "version": "0.187.456",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",