From 3e56c26bdaf7b55a9d35398ca2a18e986334edb4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Jul 2026 04:13:24 +0000 Subject: [PATCH 1/8] Initial plan From 3442d688fb91b8844f11704ebf8bf35c12ae7288 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Jul 2026 04:18:03 +0000 Subject: [PATCH 2/8] Add OpenAI Responses API (JSON Schema mode) support --- js/owrap.ai.js | 100 ++++++++++++++++++++++++++ tests/autoTestAll.AI.js | 146 ++++++++++++++++++++++++++++++++++++++ tests/autoTestAll.AI.yaml | 66 ++++++++++++----- 3 files changed, 294 insertions(+), 18 deletions(-) diff --git a/js/owrap.ai.js b/js/owrap.ai.js index e922f509..b5332ac8 100644 --- a/js/owrap.ai.js +++ b/js/owrap.ai.js @@ -698,6 +698,51 @@ OpenWrap.ai.prototype.__gpttypes = { if (isMap(res) && isDef(res.error)) return res return res.content }, + rawResponse: (aPrompt, aModel, aTemperature, aJsonSchemaFlag, aTools, aResponseSchema) => { + aPrompt = _$(aPrompt, "aPrompt").default(__) + aTemperature = _$(aTemperature, "aTemperature").isNumber().default(_temperature) + aModel = _$(aModel, "aModel").isString().default(_model) + aJsonSchemaFlag = _$(aJsonSchemaFlag, "aJsonSchemaFlag").isBoolean().default(false) + if (isUnDef(aTools)) { + aTools = Object.keys(_r.tools) + } else if (isMap(aTools)) { + aTools = Object.keys(aTools) + } + aTools = _$(aTools, "aTools").isArray().default([]) + + _resetStats() + var msgs = [] + if (isString(aPrompt)) aPrompt = [ aPrompt ] + aPrompt = _r.conversation.concat(aPrompt) + msgs = aPrompt.filter(c => isDef(c)).map(c => isMap(c) ? c : { role: "user", content: c }) + + _r.conversation = msgs + if (_noSystem) msgs = msgs.map(m => { if (m.role == "system") m.role = "developer"; return m }) + var body = { + model: aModel, + temperature: aTemperature, + messages: msgs + } + + if (isDef(aResponseSchema) && isMap(aResponseSchema)) { + body.response_format = { + type: "json_schema", + json_schema: { + name: aResponseSchema.name || "Response", + description: aResponseSchema.description || "API Response", + schema: aResponseSchema.schema || {}, + strict: aResponseSchema.strict !== false + } + } + } + + body = merge(body, aOptions.params) + if (isDef(_debugCh)) $ch(_debugCh).set({_t:nowNano(),_f:'client'}, merge({_t:nowNano(),_f:'client'}, body)) + var _res = _r._request(_route("chat/completions", aModel), body) + if (isDef(_debugCh)) $ch(_debugCh).set({_t:nowNano(),_f:'llm'}, merge({_t:nowNano(),_f:'llm'}, _res)) + _captureStats(_res, body) + return _res + }, rawImgGen: (aPrompt, aModel) => { aPrompt = _$(aPrompt, "aPrompt").default(__) aModel = _$(aModel, "aModel").isString().default(_model) @@ -3573,6 +3618,39 @@ OpenWrap.ai.prototype.gpt.prototype.rawPromptWithStats = function(aPrompt, aRole return { response: response, stats: this.getLastStats() } } +/** + * + * ow.ai.gpt.jsonSchemaPrompt(aPrompt, aResponseSchema, aModel, aTemperature, tools) : Object + * Executes a prompt using OpenAI's JSON Schema response format, enforcing structured output validated against aResponseSchema.\ + * aResponseSchema should be a map with: name (string), description (string), schema (JSON Schema object), strict (boolean, defaults to true).\ + * Returns the parsed JSON response matching the provided schema.\ + * Only supported by the "openai" provider; throws if the underlying provider does not implement rawResponse. + * + */ +OpenWrap.ai.prototype.gpt.prototype.jsonSchemaPrompt = function(aPrompt, aResponseSchema, aModel, aTemperature, tools) { + if (!isFunction(this.model.rawResponse)) throw "JSON Schema responses not supported by this provider" + var response = this.model.rawResponse(aPrompt, aModel, aTemperature, false, tools, aResponseSchema) + if (isArray(response.choices) && response.choices.length > 0) { + if (response.choices[0].finish_reason == "stop") { + var content = response.choices[0].message.content + return isString(content) ? jsonParse(content, __, __, true) : content + } + } + return response +} + +/** + * + * ow.ai.gpt.jsonSchemaPromptWithStats(aPrompt, aResponseSchema, aModel, aTemperature, tools) : Map + * Executes jsonSchemaPrompt and returns the parsed response together with any reported statistics ({ response, stats }).\ + * aResponseSchema should be a map with: name (string), description (string), schema (JSON Schema object), strict (boolean, defaults to true). + * + */ +OpenWrap.ai.prototype.gpt.prototype.jsonSchemaPromptWithStats = function(aPrompt, aResponseSchema, aModel, aTemperature, tools) { + var response = this.jsonSchemaPrompt(aPrompt, aResponseSchema, aModel, aTemperature, tools) + return { response: response, stats: this.getLastStats() } +} + /** * * ow.ai.gpt.promptImgGen(aPrompt, aModel, anOutputPathPrefix) : Array @@ -4380,6 +4458,28 @@ global.$gpt = function(aModel) { * */ importConversation: (aExport) => { _g.importConversation(aExport); return _r }, + /** + * + * $gpt.jsonSchemaPrompt(aPrompt, aResponseSchema, aModel, aTemperature, tools) : Object + * Executes a prompt using OpenAI's JSON Schema response format, enforcing structured output validated against aResponseSchema.\ + * aResponseSchema should be a map with: name (string), description (string), schema (JSON Schema object), strict (boolean, defaults to true).\ + * Returns the parsed JSON response matching the provided schema.\ + * Only supported by the "openai" provider. + * + */ + jsonSchemaPrompt: (aPrompt, aResponseSchema, aModel, aTemperature, tools) => { + return _g.jsonSchemaPrompt(aPrompt, aResponseSchema, aModel, aTemperature, tools) + }, + /** + * + * $gpt.jsonSchemaPromptWithStats(aPrompt, aResponseSchema, aModel, aTemperature, tools) : Map + * Executes jsonSchemaPrompt and returns the parsed response together with any reported statistics ({ response, stats }).\ + * aResponseSchema should be a map with: name (string), description (string), schema (JSON Schema object), strict (boolean, defaults to true). + * + */ + jsonSchemaPromptWithStats: (aPrompt, aResponseSchema, aModel, aTemperature, tools) => { + return _g.jsonSchemaPromptWithStats(aPrompt, aResponseSchema, aModel, aTemperature, tools) + }, /** * * $gpt.close() diff --git a/tests/autoTestAll.AI.js b/tests/autoTestAll.AI.js index 5e593b20..af8d2b3c 100644 --- a/tests/autoTestAll.AI.js +++ b/tests/autoTestAll.AI.js @@ -815,4 +815,150 @@ ow.test.assert(deltas, [ "part-1", "part-2" ], "Problem preserving Ollama streaming deltas after warmup."); ow.test.assert(res.content, "part-1part-2", "Problem aggregating Ollama streaming content after warmup."); }; + + exports.testAIOpenAIRawResponse = function() { + ow.loadAI(); + + var g = new ow.ai.gpt("openai", { key: "test-key", model: "gpt-4o" }); + var requests = []; + var schema = { + name: "PersonInfo", + description: "Extract person details", + schema: { + type: "object", + properties: { + name: { type: "string" }, + age: { type: "number" } + }, + required: ["name", "age"], + additionalProperties: false + }, + strict: true + }; + + g.model._request = function(url, body) { + requests.push({ url: url, body: __cloneForTest(body) }); + return { + model: "gpt-4o", + choices: [ + { + finish_reason: "stop", + message: { role: "assistant", content: '{"name":"Alice","age":30}' } + } + ], + usage: { prompt_tokens: 20, completion_tokens: 10, total_tokens: 30 } + }; + }; + + var rawRes = g.model.rawResponse("Extract: Alice, 30", "gpt-4o", 0.5, false, [], schema); + ow.test.assert(requests.length, 1, "Problem: rawResponse should make exactly one request."); + ow.test.assert(requests[0].url.indexOf("chat/completions") >= 0, true, "Problem: rawResponse should use chat/completions endpoint."); + ow.test.assert(requests[0].body.response_format.type, "json_schema", "Problem: rawResponse should set response_format.type to json_schema."); + ow.test.assert(requests[0].body.response_format.json_schema.name, "PersonInfo", "Problem: rawResponse should pass schema name."); + ow.test.assert(requests[0].body.response_format.json_schema.strict, true, "Problem: rawResponse should pass strict flag."); + ow.test.assert(isArray(rawRes.choices) && rawRes.choices.length > 0, true, "Problem: rawResponse should return choices array."); + }; + + exports.testAIOpenAIJsonSchemaPrompt = function() { + ow.loadAI(); + + var g = new ow.ai.gpt("openai", { key: "test-key", model: "gpt-4o" }); + var schema = { + name: "PersonInfo", + description: "Extract person details", + schema: { + type: "object", + properties: { + name: { type: "string" }, + age: { type: "number" } + }, + required: ["name", "age"], + additionalProperties: false + }, + strict: true + }; + + g.model._request = function(url, body) { + return { + model: "gpt-4o", + choices: [ + { + finish_reason: "stop", + message: { role: "assistant", content: '{"name":"Alice","age":30}' } + } + ], + usage: { prompt_tokens: 20, completion_tokens: 10, total_tokens: 30 } + }; + }; + + var result = g.jsonSchemaPrompt("Extract: Alice, 30", schema, "gpt-4o", 0.5); + ow.test.assert(isMap(result), true, "Problem: jsonSchemaPrompt should return a parsed map."); + ow.test.assert(result.name, "Alice", "Problem: jsonSchemaPrompt should parse name from JSON."); + ow.test.assert(result.age, 30, "Problem: jsonSchemaPrompt should parse age from JSON."); + }; + + exports.testAIOpenAIJsonSchemaPromptWithStats = function() { + ow.loadAI(); + + var g = new ow.ai.gpt("openai", { key: "test-key", model: "gpt-4o" }); + var schema = { + name: "PersonInfo", + schema: { type: "object", properties: { name: { type: "string" } }, required: ["name"], additionalProperties: false }, + strict: true + }; + + g.model._request = function(url, body) { + return { + model: "gpt-4o", + choices: [ + { + finish_reason: "stop", + message: { role: "assistant", content: '{"name":"Bob"}' } + } + ], + usage: { prompt_tokens: 15, completion_tokens: 8, total_tokens: 23 } + }; + }; + + var result = g.jsonSchemaPromptWithStats("Get name: Bob", schema); + ow.test.assert(isMap(result), true, "Problem: jsonSchemaPromptWithStats should return a map."); + ow.test.assert(isDef(result.response), true, "Problem: jsonSchemaPromptWithStats should have response field."); + ow.test.assert(isDef(result.stats), true, "Problem: jsonSchemaPromptWithStats should have stats field."); + ow.test.assert(result.response.name, "Bob", "Problem: jsonSchemaPromptWithStats response should contain parsed name."); + ow.test.assert(result.stats.tokens.total, 23, "Problem: jsonSchemaPromptWithStats stats should contain token usage."); + }; + + exports.testAIOpenAIJsonSchemaPromptSchemaDefault = function() { + ow.loadAI(); + + var g = new ow.ai.gpt("openai", { key: "test-key", model: "gpt-4o" }); + var requests = []; + + g.model._request = function(url, body) { + requests.push(__cloneForTest(body)); + return { + model: "gpt-4o", + choices: [ { finish_reason: "stop", message: { role: "assistant", content: '{"val":1}' } } ], + usage: { prompt_tokens: 5, completion_tokens: 5, total_tokens: 10 } + }; + }; + + // Schema without description or strict – should use defaults + g.model.rawResponse("test", "gpt-4o", 0.5, false, [], { name: "MySchema", schema: { type: "object", additionalProperties: false } }); + ow.test.assert(requests[0].response_format.json_schema.description, "API Response", "Problem: rawResponse should default description to 'API Response'."); + ow.test.assert(requests[0].response_format.json_schema.strict, true, "Problem: rawResponse should default strict to true."); + }; + + exports.testAIOpenAIJsonSchemaPromptUnsupportedProvider = function() { + ow.loadAI(); + + var g = new ow.ai.gpt("gemini", { key: "test-key" }); + var threw = false; + try { + g.jsonSchemaPrompt("test", { name: "T", schema: {} }); + } catch(e) { + threw = true; + } + ow.test.assert(threw, true, "Problem: jsonSchemaPrompt should throw for providers that don't support rawResponse."); + }; })(); diff --git a/tests/autoTestAll.AI.yaml b/tests/autoTestAll.AI.yaml index f7e2ec2b..1421e0e1 100644 --- a/tests/autoTestAll.AI.yaml +++ b/tests/autoTestAll.AI.yaml @@ -120,10 +120,35 @@ jobs: to : oJob Test exec: args.func = args.tests.testAIOllamaStreamingToolExecutionAndConversationIds; - - name: AI::Ollama streaming warmup on fresh conversation - from: AI::Init - to : oJob Test - exec: args.func = args.tests.testAIOllamaStreamingWarmsModelOnFreshConversation; + - name: AI::Ollama streaming warmup on fresh conversation + from: AI::Init + to : oJob Test + exec: args.func = args.tests.testAIOllamaStreamingWarmsModelOnFreshConversation; + + - name: AI::OpenAI rawResponse method + from: AI::Init + to : oJob Test + exec: args.func = args.tests.testAIOpenAIRawResponse; + + - name: AI::OpenAI jsonSchemaPrompt + from: AI::Init + to : oJob Test + exec: args.func = args.tests.testAIOpenAIJsonSchemaPrompt; + + - name: AI::OpenAI jsonSchemaPromptWithStats + from: AI::Init + to : oJob Test + exec: args.func = args.tests.testAIOpenAIJsonSchemaPromptWithStats; + + - name: AI::OpenAI jsonSchemaPrompt schema defaults + from: AI::Init + to : oJob Test + exec: args.func = args.tests.testAIOpenAIJsonSchemaPromptSchemaDefault; + + - name: AI::OpenAI jsonSchemaPrompt unsupported provider + from: AI::Init + to : oJob Test + exec: args.func = args.tests.testAIOpenAIJsonSchemaPromptUnsupportedProvider; todo: # ow.ai tests @@ -137,17 +162,22 @@ todo: - AI::LSTM network save and load - AI::Normalize with schema - AI::Normalize features array - - AI::Perceptron XOR save and load - - AI::Test ID3 - - AI::Test C45 - - AI::Test KMeans - - AI::GPT prompt argument routing - - AI::OpenAI transport modes - - AI::OpenAI stats capture includes cached and reasoning - - AI::Anthropic prompt caching headers - - AI::Anthropic prompt caching body and stats - - AI::OpenAI tool recursion without duplication - - AI::OpenAI streaming tool recursion without duplication - - AI::OpenAI export conversation falls back to tool name - - AI::Ollama streaming tool execution and ids - - AI::Ollama streaming warmup on fresh conversation + - AI::Perceptron XOR save and load + - AI::Test ID3 + - AI::Test C45 + - AI::Test KMeans + - AI::GPT prompt argument routing + - AI::OpenAI transport modes + - AI::OpenAI stats capture includes cached and reasoning + - AI::Anthropic prompt caching headers + - AI::Anthropic prompt caching body and stats + - AI::OpenAI tool recursion without duplication + - AI::OpenAI streaming tool recursion without duplication + - AI::OpenAI export conversation falls back to tool name + - AI::Ollama streaming tool execution and ids + - AI::Ollama streaming warmup on fresh conversation + - AI::OpenAI rawResponse method + - AI::OpenAI jsonSchemaPrompt + - AI::OpenAI jsonSchemaPromptWithStats + - AI::OpenAI jsonSchemaPrompt schema defaults + - AI::OpenAI jsonSchemaPrompt unsupported provider From bb3d97e2b8298ea8ea36031eb05485fc2743c95d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Jul 2026 04:20:00 +0000 Subject: [PATCH 3/8] Fix test formatting for consistency with existing test style --- tests/autoTestAll.AI.js | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/tests/autoTestAll.AI.js b/tests/autoTestAll.AI.js index af8d2b3c..d86fc725 100644 --- a/tests/autoTestAll.AI.js +++ b/tests/autoTestAll.AI.js @@ -903,7 +903,14 @@ var g = new ow.ai.gpt("openai", { key: "test-key", model: "gpt-4o" }); var schema = { name: "PersonInfo", - schema: { type: "object", properties: { name: { type: "string" } }, required: ["name"], additionalProperties: false }, + schema: { + type: "object", + properties: { + name: { type: "string" } + }, + required: ["name"], + additionalProperties: false + }, strict: true }; @@ -938,13 +945,21 @@ requests.push(__cloneForTest(body)); return { model: "gpt-4o", - choices: [ { finish_reason: "stop", message: { role: "assistant", content: '{"val":1}' } } ], + choices: [ + { + finish_reason: "stop", + message: { role: "assistant", content: '{"val":1}' } + } + ], usage: { prompt_tokens: 5, completion_tokens: 5, total_tokens: 10 } }; }; // Schema without description or strict – should use defaults - g.model.rawResponse("test", "gpt-4o", 0.5, false, [], { name: "MySchema", schema: { type: "object", additionalProperties: false } }); + g.model.rawResponse("test", "gpt-4o", 0.5, false, [], { + name: "MySchema", + schema: { type: "object", additionalProperties: false } + }); ow.test.assert(requests[0].response_format.json_schema.description, "API Response", "Problem: rawResponse should default description to 'API Response'."); ow.test.assert(requests[0].response_format.json_schema.strict, true, "Problem: rawResponse should default strict to true."); }; From 46e07dd897fb91065e48fd171ac6987671e59623 Mon Sep 17 00:00:00 2001 From: nmaguiar Date: Mon, 6 Jul 2026 14:23:12 +0100 Subject: [PATCH 4/8] feat(ai): add support for OpenAI responses API Implement the /responses endpoint integration in owrap.ai.js, including response schema normalization, input text conversion, and error handling. Update AI tests to verify the new responses endpoint usage and JSON schema formatting. --- js/owrap.ai.js | 159 ++++++++++++++++++++++++++---- tests/autoTestAll.AI.js | 200 ++++++++++++++++++++++++++++++++------ tests/autoTestAll.AI.yaml | 52 ++++++---- 3 files changed, 346 insertions(+), 65 deletions(-) diff --git a/js/owrap.ai.js b/js/owrap.ai.js index b5332ac8..584d03ab 100644 --- a/js/owrap.ai.js +++ b/js/owrap.ai.js @@ -189,6 +189,74 @@ OpenWrap.ai.prototype.__gpttypes = { return merge(_h, aOptions.headers) } var _buildURL = aURI => _joinURL(aOptions.url, aURI) + var _responsesAvailable = __ + var _normalizeResponseSchema = aResponseSchema => { + if (!isMap(aResponseSchema)) return __ + return { + name : aResponseSchema.name || "Response", + description: aResponseSchema.description || "API Response", + schema : aResponseSchema.schema || {}, + strict : aResponseSchema.strict !== false + } + } + var _toResponseInputText = aContent => { + if (isMap(aContent) || isArray(aContent)) return stringify(aContent, __, "") + return isDef(aContent) ? String(aContent) : "" + } + var _toResponsesInput = aMsgs => { + return _$(aMsgs, "aMsgs").isArray().default([]).map(m => { + if (!isMap(m)) return { role: "user", content: [ { type: "input_text", text: _toResponseInputText(m) } ] } + if (isArray(m.content)) { + return { + role : isString(m.role) ? m.role : "user", + content: clone(m.content) + } + } + return { + role : isString(m.role) ? m.role : "user", + content: [ { type: "input_text", text: _toResponseInputText(m.content) } ] + } + }) + } + var _extractResponseErrorMessage = aResponse => { + if (!isMap(aResponse)) return "" + if (isMap(aResponse.error)) { + if (isString(aResponse.error.message)) return aResponse.error.message + if (isString(aResponse.error.error)) return aResponse.error.error + return stringify(aResponse.error, __, "") + } + if (isString(aResponse.message)) return aResponse.message + return stringify(aResponse, __, "") + } + var _isResponsesUnavailable = aResponse => { + if (!isMap(aResponse)) return false + var _status = isDef(aResponse.status) ? Number(aResponse.status) : (isMap(aResponse.error) && isDef(aResponse.error.status) ? Number(aResponse.error.status) : __) + if ([ 404, 405, 410, 501 ].indexOf(_status) >= 0) return true + + var _code = (isMap(aResponse.error) && isDef(aResponse.error.code) ? aResponse.error.code : aResponse.code) + if (isDef(_code)) { + _code = String(_code).toLowerCase() + if (_code.indexOf("not_found") >= 0 || _code.indexOf("unsupported") >= 0 || _code.indexOf("unknown_url") >= 0 || _code.indexOf("unknown_endpoint") >= 0) return true + } + + var _type = (isMap(aResponse.error) && isDef(aResponse.error.type) ? aResponse.error.type : aResponse.type) + if (isDef(_type)) { + _type = String(_type).toLowerCase() + if (_type.indexOf("not_found") >= 0 || _type.indexOf("unsupported") >= 0) return true + } + + var _msg = _extractResponseErrorMessage(aResponse).toLowerCase() + return ( + _msg.indexOf("/responses") >= 0 && ( + _msg.indexOf("not found") >= 0 || + _msg.indexOf("unsupported") >= 0 || + _msg.indexOf("unknown") >= 0 || + _msg.indexOf("not available") >= 0 || + _msg.indexOf("unavailable") >= 0 || + _msg.indexOf("does not exist") >= 0 + ) + ) || _msg.indexOf("unknown endpoint") >= 0 || _msg.indexOf("unknown url") >= 0 + } var _captureStats = (aResponse, aRequestBody) => { if (!isMap(aResponse)) { _lastStats = __ @@ -207,13 +275,23 @@ OpenWrap.ai.prototype.__gpttypes = { if (isDef(aResponse.usage.prompt_tokens)) tokens.prompt = aResponse.usage.prompt_tokens if (isDef(aResponse.usage.completion_tokens)) tokens.completion = aResponse.usage.completion_tokens if (isDef(aResponse.usage.total_tokens)) tokens.total = aResponse.usage.total_tokens + if (isDef(aResponse.usage.input_tokens)) tokens.prompt = aResponse.usage.input_tokens + if (isDef(aResponse.usage.output_tokens)) tokens.completion = aResponse.usage.output_tokens + if (isDef(aResponse.usage.total_tokens)) tokens.total = aResponse.usage.total_tokens if (isMap(aResponse.usage.prompt_tokens_details)) { if (isDef(aResponse.usage.prompt_tokens_details.cached_tokens)) tokens.cached = aResponse.usage.prompt_tokens_details.cached_tokens if (isDef(aResponse.usage.prompt_tokens_details.audio_tokens)) tokens.audio = aResponse.usage.prompt_tokens_details.audio_tokens } + if (isMap(aResponse.usage.input_tokens_details)) { + if (isDef(aResponse.usage.input_tokens_details.cached_tokens)) tokens.cached = aResponse.usage.input_tokens_details.cached_tokens + if (isDef(aResponse.usage.input_tokens_details.audio_tokens)) tokens.audio = aResponse.usage.input_tokens_details.audio_tokens + } if (isMap(aResponse.usage.completion_tokens_details)) { if (isDef(aResponse.usage.completion_tokens_details.reasoning_tokens)) tokens.reasoning = aResponse.usage.completion_tokens_details.reasoning_tokens } + if (isMap(aResponse.usage.output_tokens_details)) { + if (isDef(aResponse.usage.output_tokens_details.reasoning_tokens)) tokens.reasoning = aResponse.usage.output_tokens_details.reasoning_tokens + } if (Object.keys(tokens).length > 0) stats.tokens = tokens stats.usage = aResponse.usage } @@ -718,29 +796,57 @@ OpenWrap.ai.prototype.__gpttypes = { _r.conversation = msgs if (_noSystem) msgs = msgs.map(m => { if (m.role == "system") m.role = "developer"; return m }) - var body = { - model: aModel, + var _schema = _normalizeResponseSchema(aResponseSchema) + var _responsesBody = { + model : aModel, temperature: aTemperature, - messages: msgs + input : _toResponsesInput(msgs) + } + if (isDef(_schema)) { + _responsesBody.text = { + format: merge({ type: "json_schema" }, _schema) + } } - if (isDef(aResponseSchema) && isMap(aResponseSchema)) { - body.response_format = { - type: "json_schema", - json_schema: { - name: aResponseSchema.name || "Response", - description: aResponseSchema.description || "API Response", - schema: aResponseSchema.schema || {}, - strict: aResponseSchema.strict !== false - } + var _chatBody = { + model : aModel, + temperature: aTemperature, + messages : msgs + } + if (isDef(_schema)) { + _chatBody.response_format = { + type : "json_schema", + json_schema: _schema } } - body = merge(body, aOptions.params) - if (isDef(_debugCh)) $ch(_debugCh).set({_t:nowNano(),_f:'client'}, merge({_t:nowNano(),_f:'client'}, body)) - var _res = _r._request(_route("chat/completions", aModel), body) - if (isDef(_debugCh)) $ch(_debugCh).set({_t:nowNano(),_f:'llm'}, merge({_t:nowNano(),_f:'llm'}, _res)) - _captureStats(_res, body) + _responsesBody = merge(_responsesBody, aOptions.params) + _chatBody = merge(_chatBody, aOptions.params) + + var _res = __ + var _requestBody = __ + if (_responsesAvailable !== false) { + _requestBody = _responsesBody + if (isDef(_debugCh)) $ch(_debugCh).set({_t:nowNano(),_f:'client'}, merge({_t:nowNano(),_f:'client'}, _requestBody)) + _res = _r._request(_route("responses", aModel), _requestBody) + if (isDef(_debugCh)) $ch(_debugCh).set({_t:nowNano(),_f:'llm'}, merge({_t:nowNano(),_f:'llm'}, _res)) + if (isMap(_res) && isMap(_res.error) && _isResponsesUnavailable(_res)) { + _responsesAvailable = false + _requestBody = _chatBody + if (isDef(_debugCh)) $ch(_debugCh).set({_t:nowNano(),_f:'client'}, merge({_t:nowNano(),_f:'client'}, _requestBody)) + _res = _r._request(_route("chat/completions", aModel), _requestBody) + if (isDef(_debugCh)) $ch(_debugCh).set({_t:nowNano(),_f:'llm'}, merge({_t:nowNano(),_f:'llm'}, _res)) + } else if (!isMap(_res) || isUnDef(_res.error)) { + _responsesAvailable = true + } + } else { + _requestBody = _chatBody + if (isDef(_debugCh)) $ch(_debugCh).set({_t:nowNano(),_f:'client'}, merge({_t:nowNano(),_f:'client'}, _requestBody)) + _res = _r._request(_route("chat/completions", aModel), _requestBody) + if (isDef(_debugCh)) $ch(_debugCh).set({_t:nowNano(),_f:'llm'}, merge({_t:nowNano(),_f:'llm'}, _res)) + } + + _captureStats(_res, _requestBody) return _res }, rawImgGen: (aPrompt, aModel) => { @@ -3630,6 +3736,25 @@ OpenWrap.ai.prototype.gpt.prototype.rawPromptWithStats = function(aPrompt, aRole OpenWrap.ai.prototype.gpt.prototype.jsonSchemaPrompt = function(aPrompt, aResponseSchema, aModel, aTemperature, tools) { if (!isFunction(this.model.rawResponse)) throw "JSON Schema responses not supported by this provider" var response = this.model.rawResponse(aPrompt, aModel, aTemperature, false, tools, aResponseSchema) + if (isString(response.output_text) && response.output_text.length > 0) { + return jsonParse(response.output_text, __, __, true) + } + if (isArray(response.output)) { + var _parts = [] + response.output.forEach(o => { + if (isArray(o.content)) { + o.content.forEach(c => { + if (isString(c.text)) _parts.push(c.text) + else if (isDef(c.json)) _parts.push(c.json) + }) + } + }) + if (_parts.length > 0) { + var _out = _parts[0] + if (_parts.length > 1 && _parts.every(isString)) _out = _parts.join("") + return isString(_out) ? jsonParse(_out, __, __, true) : _out + } + } if (isArray(response.choices) && response.choices.length > 0) { if (response.choices[0].finish_reason == "stop") { var content = response.choices[0].message.content diff --git a/tests/autoTestAll.AI.js b/tests/autoTestAll.AI.js index d86fc725..5b60013e 100644 --- a/tests/autoTestAll.AI.js +++ b/tests/autoTestAll.AI.js @@ -404,11 +404,13 @@ var openai = new ow.ai.gpt("openai", { key: "test-key", url: "https://api.openai.com" }); var openaiTransport = openai.model.__debugTransport("chat/completions", "gpt-test"); ow.test.assert(openaiTransport.url, "https://api.openai.com/v1/chat/completions", "Problem building default OpenAI chat URL."); + ow.test.assert(openai.model.__debugTransport("responses", "gpt-test").url, "https://api.openai.com/v1/responses", "Problem building default OpenAI responses URL."); ow.test.assert(isDef(openaiTransport.headers.Authorization), true, "Problem setting default OpenAI bearer authorization."); ow.test.assert(isUnDef(openaiTransport.headers["api-key"]), true, "Problem avoiding api-key on default OpenAI transport."); var openaiBaseUrl = new ow.ai.gpt("openai", { key: "test-key", url: "https://api.openai.com/v1" }); ow.test.assert(openaiBaseUrl.model.__debugTransport("chat/completions", "gpt-test").url, "https://api.openai.com/v1/chat/completions", "Problem preserving an OpenAI v1 base URL."); + ow.test.assert(openaiBaseUrl.model.__debugTransport("responses", "gpt-test").url, "https://api.openai.com/v1/responses", "Problem preserving an OpenAI v1 responses URL."); var azureV1 = new ow.ai.gpt("openai", { key : "test-key", @@ -417,6 +419,7 @@ }); var azureV1Transport = azureV1.model.__debugTransport("chat/completions", "deployment-a"); ow.test.assert(azureV1Transport.url, "https://example.openai.azure.com/openai/v1/chat/completions", "Problem building Azure OpenAI v1 chat URL."); + ow.test.assert(azureV1.model.__debugTransport("responses", "deployment-a").url, "https://example.openai.azure.com/openai/v1/responses", "Problem building Azure OpenAI v1 responses URL."); ow.test.assert("" + azureV1Transport.headers["api-key"], "test-key", "Problem setting Azure OpenAI v1 api-key header."); ow.test.assert(isUnDef(azureV1Transport.headers.Authorization), true, "Problem suppressing Azure OpenAI v1 bearer authorization."); @@ -426,6 +429,7 @@ mode: "azure-openai-v1" }); ow.test.assert(azureV1BaseUrl.model.__debugTransport("chat/completions", "deployment-a").url, "https://example.openai.azure.com/openai/v1/chat/completions", "Problem preserving an Azure OpenAI v1 base URL."); + ow.test.assert(azureV1BaseUrl.model.__debugTransport("responses", "deployment-a").url, "https://example.openai.azure.com/openai/v1/responses", "Problem preserving an Azure OpenAI v1 responses URL."); var azureLegacy = new ow.ai.gpt("openai", { key : "test-key", @@ -436,6 +440,7 @@ }); var azureLegacyTransport = azureLegacy.model.__debugTransport("chat/completions", "ignored"); ow.test.assert(azureLegacyTransport.url, "https://example.openai.azure.com/openai/deployments/deployment-a/chat/completions?api-version=2024-10-21", "Problem building Azure OpenAI legacy deployment chat URL."); + ow.test.assert(azureLegacy.model.__debugTransport("responses", "ignored").url, "https://example.openai.azure.com/openai/deployments/deployment-a/responses?api-version=2024-10-21", "Problem building Azure OpenAI legacy deployment responses URL."); ow.test.assert("" + azureLegacyTransport.headers["api-key"], "test-key", "Problem setting Azure OpenAI legacy api-key header."); var foundryV1 = new ow.ai.gpt("openai", { @@ -445,6 +450,7 @@ }); var foundryV1Transport = foundryV1.model.__debugTransport("chat/completions", "deployment-a"); ow.test.assert(foundryV1Transport.url, "https://example.services.ai.azure.com/openai/v1/chat/completions", "Problem building Foundry v1 chat URL."); + ow.test.assert(foundryV1.model.__debugTransport("responses", "deployment-a").url, "https://example.services.ai.azure.com/openai/v1/responses", "Problem building Foundry v1 responses URL."); ow.test.assert("" + foundryV1Transport.headers["api-key"], "test-key", "Problem setting Foundry v1 api-key header."); var foundryPreview = new ow.ai.gpt("openai", { @@ -455,6 +461,7 @@ }); var foundryPreviewTransport = foundryPreview.model.__debugTransport("chat/completions", "deployment-a"); ow.test.assert(foundryPreviewTransport.url, "https://example.services.ai.azure.com/models/chat/completions?api-version=2024-05-01-preview", "Problem building Foundry model inference preview chat URL."); + ow.test.assert(foundryPreview.model.__debugTransport("responses", "deployment-a").url, "https://example.services.ai.azure.com/models/responses?api-version=2024-05-01-preview", "Problem building Foundry model inference preview responses URL."); ow.test.assert("" + foundryPreviewTransport.headers["api-key"], "test-key", "Problem setting Foundry preview api-key header."); }; @@ -840,23 +847,26 @@ requests.push({ url: url, body: __cloneForTest(body) }); return { model: "gpt-4o", - choices: [ + output_text: '{"name":"Alice","age":30}', + output: [ { - finish_reason: "stop", - message: { role: "assistant", content: '{"name":"Alice","age":30}' } + content: [ + { type: "output_text", text: '{"name":"Alice","age":30}' } + ] } ], - usage: { prompt_tokens: 20, completion_tokens: 10, total_tokens: 30 } + usage: { input_tokens: 20, output_tokens: 10, total_tokens: 30 } }; }; var rawRes = g.model.rawResponse("Extract: Alice, 30", "gpt-4o", 0.5, false, [], schema); ow.test.assert(requests.length, 1, "Problem: rawResponse should make exactly one request."); - ow.test.assert(requests[0].url.indexOf("chat/completions") >= 0, true, "Problem: rawResponse should use chat/completions endpoint."); - ow.test.assert(requests[0].body.response_format.type, "json_schema", "Problem: rawResponse should set response_format.type to json_schema."); - ow.test.assert(requests[0].body.response_format.json_schema.name, "PersonInfo", "Problem: rawResponse should pass schema name."); - ow.test.assert(requests[0].body.response_format.json_schema.strict, true, "Problem: rawResponse should pass strict flag."); - ow.test.assert(isArray(rawRes.choices) && rawRes.choices.length > 0, true, "Problem: rawResponse should return choices array."); + ow.test.assert(requests[0].url.indexOf("responses") >= 0, true, "Problem: rawResponse should prefer the responses endpoint."); + ow.test.assert(requests[0].body.text.format.type, "json_schema", "Problem: rawResponse should set text.format.type to json_schema."); + ow.test.assert(requests[0].body.text.format.name, "PersonInfo", "Problem: rawResponse should pass schema name."); + ow.test.assert(requests[0].body.text.format.strict, true, "Problem: rawResponse should pass strict flag."); + ow.test.assert(requests[0].body.input[0].content[0].text, "Extract: Alice, 30", "Problem: rawResponse should convert prompt messages to responses input."); + ow.test.assert(rawRes.output_text, '{"name":"Alice","age":30}', "Problem: rawResponse should return the responses payload."); }; exports.testAIOpenAIJsonSchemaPrompt = function() { @@ -881,13 +891,14 @@ g.model._request = function(url, body) { return { model: "gpt-4o", - choices: [ + output: [ { - finish_reason: "stop", - message: { role: "assistant", content: '{"name":"Alice","age":30}' } + content: [ + { type: "output_text", text: '{"name":"Alice","age":30}' } + ] } ], - usage: { prompt_tokens: 20, completion_tokens: 10, total_tokens: 30 } + usage: { input_tokens: 20, output_tokens: 10, total_tokens: 30 } }; }; @@ -917,13 +928,15 @@ g.model._request = function(url, body) { return { model: "gpt-4o", - choices: [ + output_text: '{"name":"Bob"}', + output: [ { - finish_reason: "stop", - message: { role: "assistant", content: '{"name":"Bob"}' } + content: [ + { type: "output_text", text: '{"name":"Bob"}' } + ] } ], - usage: { prompt_tokens: 15, completion_tokens: 8, total_tokens: 23 } + usage: { input_tokens: 15, output_tokens: 8, total_tokens: 23 } }; }; @@ -945,13 +958,15 @@ requests.push(__cloneForTest(body)); return { model: "gpt-4o", - choices: [ + output_text: '{"val":1}', + output: [ { - finish_reason: "stop", - message: { role: "assistant", content: '{"val":1}' } + content: [ + { type: "output_text", text: '{"val":1}' } + ] } ], - usage: { prompt_tokens: 5, completion_tokens: 5, total_tokens: 10 } + usage: { input_tokens: 5, output_tokens: 5, total_tokens: 10 } }; }; @@ -960,20 +975,143 @@ name: "MySchema", schema: { type: "object", additionalProperties: false } }); - ow.test.assert(requests[0].response_format.json_schema.description, "API Response", "Problem: rawResponse should default description to 'API Response'."); - ow.test.assert(requests[0].response_format.json_schema.strict, true, "Problem: rawResponse should default strict to true."); + ow.test.assert(requests[0].text.format.description, "API Response", "Problem: rawResponse should default description to 'API Response'."); + ow.test.assert(requests[0].text.format.strict, true, "Problem: rawResponse should default strict to true."); + }; + + exports.testAIOpenAIRawResponseFallsBackToChatCompletions = function() { + ow.loadAI(); + + var g = new ow.ai.gpt("openai", { key: "test-key", model: "gpt-4o" }); + var requests = []; + var schema = { + name: "PersonInfo", + schema: { + type: "object", + properties: { + name: { type: "string" } + }, + required: ["name"], + additionalProperties: false + } + }; + + g.model._request = function(url, body) { + requests.push({ url: url, body: __cloneForTest(body) }); + if (url.indexOf("responses") >= 0) { + return { + error: { + message: "The /responses endpoint is not available for this deployment.", + code: "not_found" + }, + status: 404 + }; + } + return { + model: "gpt-4o", + choices: [ + { + finish_reason: "stop", + message: { role: "assistant", content: '{"name":"Alice"}' } + } + ], + usage: { prompt_tokens: 8, completion_tokens: 4, total_tokens: 12 } + }; + }; + + var rawRes = g.model.rawResponse("Extract: Alice", "gpt-4o", 0.5, false, [], schema); + ow.test.assert(requests.length, 2, "Problem: rawResponse should retry with chat/completions when responses is unavailable."); + ow.test.assert(requests[0].url.indexOf("responses") >= 0, true, "Problem: rawResponse should try responses first."); + ow.test.assert(requests[1].url.indexOf("chat/completions") >= 0, true, "Problem: rawResponse should fall back to chat/completions."); + ow.test.assert(requests[1].body.response_format.type, "json_schema", "Problem: chat fallback should keep json_schema response_format."); + ow.test.assert(isArray(rawRes.choices) && rawRes.choices.length > 0, true, "Problem: chat fallback should return the chat response payload."); + }; + + exports.testAIOpenAIRawResponseCachesChatFallback = function() { + ow.loadAI(); + + var g = new ow.ai.gpt("openai", { key: "test-key", model: "gpt-4o" }); + var requests = []; + var schema = { + name: "PersonInfo", + schema: { + type: "object", + properties: { + name: { type: "string" } + }, + required: ["name"], + additionalProperties: false + } + }; + + g.model._request = function(url, body) { + requests.push(url); + if (url.indexOf("responses") >= 0) { + return { + error: { + message: "The /responses endpoint is not available for this deployment.", + code: "not_found" + }, + status: 404 + }; + } + return { + model: "gpt-4o", + choices: [ + { + finish_reason: "stop", + message: { role: "assistant", content: '{"name":"Alice"}' } + } + ], + usage: { prompt_tokens: 8, completion_tokens: 4, total_tokens: 12 } + }; + }; + + g.model.rawResponse("Extract: Alice", "gpt-4o", 0.5, false, [], schema); + g.model.rawResponse("Extract: Alice again", "gpt-4o", 0.5, false, [], schema); + ow.test.assert(requests.filter(r => r.indexOf("responses") >= 0).length, 1, "Problem: rawResponse should stop retrying responses after endpoint unavailability."); + ow.test.assert(requests.filter(r => r.indexOf("chat/completions") >= 0).length, 2, "Problem: rawResponse should reuse chat/completions fallback after responses becomes unavailable."); + }; + + exports.testAIOpenAIRawResponseDoesNotFallbackOnOtherErrors = function() { + ow.loadAI(); + + var g = new ow.ai.gpt("openai", { key: "test-key", model: "gpt-4o" }); + var requests = []; + + g.model._request = function(url, body) { + requests.push(url); + return { + error: { + message: "Invalid schema for text.format", + code: "invalid_request_error" + }, + status: 400 + }; + }; + + var res = g.model.rawResponse("Extract: Alice", "gpt-4o", 0.5, false, [], { + name: "PersonInfo", + schema: { type: "object" } + }); + ow.test.assert(requests.length, 1, "Problem: rawResponse should not fall back for non-availability errors."); + ow.test.assert(requests[0].indexOf("responses") >= 0, true, "Problem: rawResponse should return the original responses error."); + ow.test.assert(isMap(res.error), true, "Problem: rawResponse should return the original error response."); }; exports.testAIOpenAIJsonSchemaPromptUnsupportedProvider = function() { ow.loadAI(); - var g = new ow.ai.gpt("gemini", { key: "test-key" }); - var threw = false; - try { - g.jsonSchemaPrompt("test", { name: "T", schema: {} }); - } catch(e) { - threw = true; - } - ow.test.assert(threw, true, "Problem: jsonSchemaPrompt should throw for providers that don't support rawResponse."); + [ "gemini", "ollama", "anthropic" ].forEach(provider => { + var g = provider == "ollama" ? new ow.ai.gpt(provider, { url: "http://127.0.0.1:11434", model: "llama-test" }) : new ow.ai.gpt(provider, { key: "test-key" }); + var threw = false; + try { + g.jsonSchemaPrompt("test", { name: "T", schema: {} }); + } catch(e) { + threw = true; + ow.test.assert(String(e), "JSON Schema responses not supported by this provider", "Problem: unsupported providers should throw a consistent error."); + } + ow.test.assert(threw, true, "Problem: jsonSchemaPrompt should throw for providers that don't support rawResponse."); + }); }; })(); diff --git a/tests/autoTestAll.AI.yaml b/tests/autoTestAll.AI.yaml index 1421e0e1..bf5199a4 100644 --- a/tests/autoTestAll.AI.yaml +++ b/tests/autoTestAll.AI.yaml @@ -125,10 +125,10 @@ jobs: to : oJob Test exec: args.func = args.tests.testAIOllamaStreamingWarmsModelOnFreshConversation; - - name: AI::OpenAI rawResponse method - from: AI::Init - to : oJob Test - exec: args.func = args.tests.testAIOpenAIRawResponse; + - name: AI::OpenAI rawResponse prefers responses + from: AI::Init + to : oJob Test + exec: args.func = args.tests.testAIOpenAIRawResponse; - name: AI::OpenAI jsonSchemaPrompt from: AI::Init @@ -140,15 +140,30 @@ jobs: to : oJob Test exec: args.func = args.tests.testAIOpenAIJsonSchemaPromptWithStats; - - name: AI::OpenAI jsonSchemaPrompt schema defaults - from: AI::Init - to : oJob Test - exec: args.func = args.tests.testAIOpenAIJsonSchemaPromptSchemaDefault; - - - name: AI::OpenAI jsonSchemaPrompt unsupported provider - from: AI::Init - to : oJob Test - exec: args.func = args.tests.testAIOpenAIJsonSchemaPromptUnsupportedProvider; + - name: AI::OpenAI jsonSchemaPrompt schema defaults + from: AI::Init + to : oJob Test + exec: args.func = args.tests.testAIOpenAIJsonSchemaPromptSchemaDefault; + + - name: AI::OpenAI rawResponse fallback to chat completions + from: AI::Init + to : oJob Test + exec: args.func = args.tests.testAIOpenAIRawResponseFallsBackToChatCompletions; + + - name: AI::OpenAI rawResponse caches chat fallback + from: AI::Init + to : oJob Test + exec: args.func = args.tests.testAIOpenAIRawResponseCachesChatFallback; + + - name: AI::OpenAI rawResponse keeps non availability errors + from: AI::Init + to : oJob Test + exec: args.func = args.tests.testAIOpenAIRawResponseDoesNotFallbackOnOtherErrors; + + - name: AI::OpenAI jsonSchemaPrompt unsupported provider + from: AI::Init + to : oJob Test + exec: args.func = args.tests.testAIOpenAIJsonSchemaPromptUnsupportedProvider; todo: # ow.ai tests @@ -176,8 +191,11 @@ todo: - AI::OpenAI export conversation falls back to tool name - AI::Ollama streaming tool execution and ids - AI::Ollama streaming warmup on fresh conversation - - AI::OpenAI rawResponse method - - AI::OpenAI jsonSchemaPrompt - - AI::OpenAI jsonSchemaPromptWithStats - - AI::OpenAI jsonSchemaPrompt schema defaults + - AI::OpenAI rawResponse prefers responses + - AI::OpenAI jsonSchemaPrompt + - AI::OpenAI jsonSchemaPromptWithStats + - AI::OpenAI jsonSchemaPrompt schema defaults + - AI::OpenAI rawResponse fallback to chat completions + - AI::OpenAI rawResponse caches chat fallback + - AI::OpenAI rawResponse keeps non availability errors - AI::OpenAI jsonSchemaPrompt unsupported provider From 0046b9eabfe5614e4d635be949071fe3493ee449 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 22:52:21 +0000 Subject: [PATCH 5/8] chore(deps): bump org.snmp4j:snmp4j-agent from 3.8.3 to 3.9.1 Bumps org.snmp4j:snmp4j-agent from 3.8.3 to 3.9.1. --- updated-dependencies: - dependency-name: org.snmp4j:snmp4j-agent dependency-version: 3.9.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8ead1fe2..6077f845 100644 --- a/pom.xml +++ b/pom.xml @@ -310,7 +310,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/ma org.snmp4j snmp4j-agent - 3.8.3 + 3.9.1 io.jsonwebtoken From 926e4a4158b99d58ddd37ed37c9751a8e70254c9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:01:49 +0000 Subject: [PATCH 6/8] Test results badge --- tests/results.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/results.svg b/tests/results.svg index 5f998751..bd046c55 100644 --- a/tests/results.svg +++ b/tests/results.svg @@ -1 +1 @@ -OpenAF tests: (FAIL: 0, PASS: 194, TOTAL: 194) \ No newline at end of file +OpenAF tests: (FAIL: 10, PASS: 228, TOTAL: 238) \ No newline at end of file From 4a26c770740d8a4f9d91ff38ebdcb1ba70814235 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:01:50 +0000 Subject: [PATCH 7/8] Test results badge --- tests/asserts.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/asserts.svg b/tests/asserts.svg index 3c29f2c2..5a880bd4 100644 --- a/tests/asserts.svg +++ b/tests/asserts.svg @@ -1 +1 @@ -OpenAF tests asserts: 615 \ No newline at end of file +OpenAF tests asserts: 820 \ No newline at end of file From 1d7c49f880a0e021908316b4ef6d9e2af1adf912 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:01:52 +0000 Subject: [PATCH 8/8] Test results badge --- tests/autoTestAll.md | 326 ++++++++++++++++++++++++------------------- 1 file changed, 185 insertions(+), 141 deletions(-) diff --git a/tests/autoTestAll.md b/tests/autoTestAll.md index ffa71c30..2b4017f3 100644 --- a/tests/autoTestAll.md +++ b/tests/autoTestAll.md @@ -2,205 +2,249 @@ ## Summary -* Number of tests performed: 194 -* Number of tests passed: 194 -* Number of tests failed: 0 -* Number of asserts: 615 +* Number of tests performed: 238 +* Number of tests passed: 228 +* Number of tests failed: 10 +* Number of asserts: 820 ## Result details | Suite | Test | Status | Time | Result | |-------|------|--------|------|--------------| -|AI | LSTM AND |   PASS   | 153 ms | n/a | -|AI | LSTM network save and load |   PASS   | 179 ms | n/a | -|AI | Liquid XOR |   PASS   | 360 ms | n/a | -|AI | Liquid network save and load |   PASS   | 342 ms | n/a | +|AI | Anthropic prompt caching body and stats |   PASS   | 9 ms | n/a | +|AI | Anthropic prompt caching headers |   FAIL   | 301 ms | TypeError: Cannot read property "requestHeaders" from undefined | +|AI | GPT prompt argument routing |   FAIL   | 9 ms | Problem routing GPT rawPromptStream wrapper arguments. (got [ "hello", "gpt-test", 0.4, false, [ { "type": "function", "function": { "name": "echo" } } ], null ] but expected ["hello","gpt-test",0.4,false,[{"type":"function","function":{"name":"echo"}}],null]) | +|AI | LSTM AND |   PASS   | 197 ms | n/a | +|AI | LSTM network save and load |   PASS   | 143 ms | n/a | +|AI | Liquid XOR |   PASS   | 343 ms | n/a | +|AI | Liquid network save and load |   PASS   | 369 ms | n/a | |AI | Normalize features array |   PASS   | 1 ms | n/a | -|AI | Normalize with schema |   PASS   | 10 ms | n/a | -|AI | Perceptron XOR |   PASS   | 709 ms | n/a | -|AI | Perceptron XOR Put |   PASS   | 6 seconds, 829 ms | n/a | -|AI | Perceptron XOR save and load |   PASS   | 47 ms | n/a | -|AI | Perceptron network save and load |   PASS   | 36 ms | n/a | -|AI | Test C45 |   PASS   | 29 ms | n/a | -|AI | Test ID3 |   PASS   | 59 ms | n/a | -|AI | Test KMeans |   PASS   | 14 ms | n/a | -|CSV | Generic CSV |   PASS   | 3 ms | n/a | -|CSV | Stream CSV |   PASS   | 23 ms | n/a | -|CSV | ToFrom CSV |   PASS   | 12 ms | n/a | -|Channels | Keep history util |   PASS   | 4 seconds, 6 ms | n/a | -|Channels | Remote channel access auditing |   PASS   | 256 ms | n/a | -|Channels | Subscribers test |   PASS   | 731 ms | n/a | -|Channels | Test Elastic Index |   PASS   | 1 ms | n/a | -|Channels | Test MVS Utils |   PASS   | 74 ms | n/a | -|Channels | Test channel (all) |   PASS   | 14 ms | n/a | +|AI | Normalize with schema |   PASS   | 6 ms | n/a | +|AI | Ollama streaming tool execution and ids |   FAIL   | 32 ms | Problem preserving Ollama streaming tool result id. (got undefined but expected "call-3") | +|AI | Ollama streaming warmup on fresh conversation |   FAIL   | 5 ms | Problem preserving Ollama streaming deltas after warmup. (got [] but expected ["part-1","part-2"]) | +|AI | OpenAI export conversation falls back to tool name |   PASS   | 5 ms | n/a | +|AI | OpenAI jsonSchemaPrompt |   PASS   | 5 ms | n/a | +|AI | OpenAI jsonSchemaPrompt schema defaults |   PASS   | 4 ms | n/a | +|AI | OpenAI jsonSchemaPrompt unsupported provider |   PASS   | 6 ms | n/a | +|AI | OpenAI jsonSchemaPromptWithStats |   PASS   | 4 ms | n/a | +|AI | OpenAI rawResponse caches chat fallback |   PASS   | 6 ms | n/a | +|AI | OpenAI rawResponse fallback to chat completions |   PASS   | 3 ms | n/a | +|AI | OpenAI rawResponse keeps non availability errors |   PASS   | 3 ms | n/a | +|AI | OpenAI rawResponse prefers responses |   PASS   | 8 ms | n/a | +|AI | OpenAI stats capture includes cached and reasoning |   PASS   | 12 ms | n/a | +|AI | OpenAI streaming tool recursion without duplication |   PASS   | 26 ms | n/a | +|AI | OpenAI tool recursion without duplication |   PASS   | 12 ms | n/a | +|AI | OpenAI transport modes |   PASS   | 26 ms | n/a | +|AI | Perceptron XOR |   PASS   | 481 ms | n/a | +|AI | Perceptron XOR Put |   PASS   | 7 seconds, 254 ms | n/a | +|AI | Perceptron XOR save and load |   PASS   | 27 ms | n/a | +|AI | Perceptron network save and load |   PASS   | 38 ms | n/a | +|AI | Test C45 |   PASS   | 30 ms | n/a | +|AI | Test ID3 |   PASS   | 48 ms | n/a | +|AI | Test KMeans |   PASS   | 12 ms | n/a | +|CSV | Generic CSV |   PASS   | 1 ms | n/a | +|CSV | Stream CSV |   PASS   | 14 ms | n/a | +|CSV | ToFrom CSV |   PASS   | 7 ms | n/a | +|Channels | Keep history util |   PASS   | 4 seconds, 8 ms | n/a | +|Channels | Remote channel access auditing |   PASS   | 228 ms | n/a | +|Channels | Subscribers test |   PASS   | 754 ms | n/a | +|Channels | Test Elastic Index |   PASS   | 2 ms | n/a | +|Channels | Test MVS Utils |   PASS   | 83 ms | n/a | +|Channels | Test channel (all) |   PASS   | 18 ms | n/a | |Channels | Test channel (big) |   PASS   | 1 ms | n/a | -|Channels | Test channel (big) |   PASS   | 22 ms | n/a | -|Channels | Test channel (big) |   PASS   | 235 ms | n/a | -|Channels | Test channel (big) |   PASS   | ~0 ms | n/a | -|Channels | Test channel (big) |   PASS   | 32 seconds, 7 ms | n/a | -|Channels | Test channel (cache) |   PASS   | 2 ms | n/a | +|Channels | Test channel (big) |   PASS   | 17 ms | n/a | +|Channels | Test channel (big) |   PASS   | 164 ms | n/a | +|Channels | Test channel (big) |   PASS   | 1 ms | n/a | +|Channels | Test channel (big) |   PASS   | 32 seconds, 5 ms | n/a | +|Channels | Test channel (cache) |   PASS   | 1 ms | n/a | |Channels | Test channel (cache) |   PASS   | 1 ms | n/a | -|Channels | Test channel (cache) |   PASS   | 32 seconds, 12 ms | n/a | +|Channels | Test channel (cache) |   PASS   | 32 seconds, 6 ms | n/a | |Channels | Test channel (file) |   PASS   | 4 ms | n/a | -|Channels | Test channel (file) |   PASS   | 19 ms | n/a | -|Channels | Test channel (file) |   PASS   | 185 ms | n/a | +|Channels | Test channel (file) |   PASS   | 11 ms | n/a | +|Channels | Test channel (file) |   PASS   | 177 ms | n/a | |Channels | Test channel (file) |   PASS   | ~0 ms | n/a | -|Channels | Test channel (file) |   PASS   | 32 seconds, 4 ms | n/a | -|Channels | Test channel (mvs) |   PASS   | 1 ms | n/a | -|Channels | Test channel (mvs) |   PASS   | 9 ms | n/a | -|Channels | Test channel (mvs) |   PASS   | 109 ms | n/a | -|Channels | Test channel (mvs) |   PASS   | 1 ms | n/a | -|Channels | Test channel (mvs) |   PASS   | 32 seconds, 4 ms | n/a | -|Channels | Test channel (simple) |   PASS   | ~0 ms | n/a | +|Channels | Test channel (file) |   PASS   | 32 seconds, 6 ms | n/a | +|Channels | Test channel (mvs) |   PASS   | 2 ms | n/a | +|Channels | Test channel (mvs) |   PASS   | 8 ms | n/a | +|Channels | Test channel (mvs) |   PASS   | 111 ms | n/a | +|Channels | Test channel (mvs) |   PASS   | ~0 ms | n/a | +|Channels | Test channel (mvs) |   PASS   | 32 seconds, 5 ms | n/a | +|Channels | Test channel (simple) |   PASS   | 1 ms | n/a | |Channels | Test channel (simple) |   PASS   | 4 ms | n/a | -|Channels | Test channel (simple) |   PASS   | 93 ms | n/a | +|Channels | Test channel (simple) |   PASS   | 113 ms | n/a | |Channels | Test channel (simple) |   PASS   | ~0 ms | n/a | |Channels | Test channel (simple) |   PASS   | 32 seconds, 5 ms | n/a | |DB | DB type conversion to JS |   PASS   | 7 ms | n/a | -|DB | Simple DB in memory |   PASS   | 175 ms | n/a | -|Format | Add number separator |   PASS   | 1 ms | n/a | +|DB | Simple DB in memory |   PASS   | 199 ms | n/a | +|Format | Add number separator |   PASS   | ~0 ms | n/a | |Format | Conversions |   PASS   | 6 ms | n/a | -|Format | Cron |   PASS   | 3 ms | n/a | -|Format | Cron How Many Ago |   PASS   | 29 ms | n/a | -|Format | Date diff |   PASS   | 5 ms | n/a | -|Format | Date to/from |   PASS   | 2 ms | n/a | +|Format | Cron |   PASS   | 2 ms | n/a | +|Format | Cron How Many Ago |   PASS   | 26 ms | n/a | +|Format | Date diff |   PASS   | 4 ms | n/a | +|Format | Date to/from |   PASS   | 5 ms | n/a | |Format | Escape strings |   PASS   | 1 ms | n/a | |Format | Escape/Unescape HTML4 |   PASS   | 11 ms | n/a | -|Format | Host |   PASS   | 3 ms | n/a | +|Format | Grid |   PASS   | 59 ms | n/a | +|Format | Host |   PASS   | 2 ms | n/a | |Format | LDAP date to/from |   PASS   | ~0 ms | n/a | |Format | LSH |   PASS   | 591 ms | n/a | |Format | Load Format |   PASS   | ~0 ms | n/a | +|Format | Markdown wrap |   PASS   | 52 ms | n/a | |Format | Number abbreviation |   PASS   | 1 ms | n/a | -|Format | Number rounding |   PASS   | ~0 ms | n/a | -|Format | SLON |   PASS   | 26 ms | n/a | +|Format | Number rounding |   PASS   | 1 ms | n/a | +|Format | Print bullet value formats |   PASS   | 17 ms | n/a | +|Format | Print dashboard |   FAIL   | 13 ms | Problem with printDashboard total width. (got 38 but expected 40) | +|Format | Print histogram |   PASS   | 15 ms | n/a | +|Format | Print sparkline |   PASS   | 7 ms | n/a | +|Format | PrintTable emoji alignment |   PASS   | 17 ms | n/a | +|Format | PrintTable header alignment |   FAIL   | 11 ms | Problem with printTable header second separator alignment. (got 13 but expected 15) | +|Format | PrintTable subdivision flag alignment |   FAIL   | 9 ms | Problem with printTable subdivision flag alignment. (got 23 but expected 13) | +|Format | SLON |   PASS   | 35 ms | n/a | |Format | String pad |   PASS   | ~0 ms | n/a | +|Format | Terminal capabilities |   PASS   | 3 ms | n/a | |Format | Time ago |   PASS   | 2 ms | n/a | |Format | TimeAbbreviation |   PASS   | 4 ms | n/a | |Format | Unix date to/from |   PASS   | 1 ms | n/a | -|Format | Word wrap |   PASS   | 6 ms | n/a | -|HTTP | HTTP changing user agent |   PASS   | 331 ms | n/a | -|HTTP | HTTP plugin basic functionality |   PASS   | 53 ms | n/a | -|HTTP | HTTP plugin test basic auth |   PASS   | 623 ms | n/a | -|HTTP | HTTP plugin web socket client |   PASS   | 3 seconds, 680 ms | n/a | -|IO | IO Test Gzip Native to Byte array |   PASS   | 28 ms | n/a | -|IO | IO Test JSON |   PASS   | 2 ms | n/a | -|IO | IO Test Stream JSON |   PASS   | 34 ms | n/a | -|IO | IO Test TAR functionality |   PASS   | 4 seconds, 736 ms | n/a | -|IO | IO Test binary file detection |   PASS   | 13 ms | n/a | -|IO | IO Test copy streams |   PASS   | 220 ms | n/a | +|Format | Viz benchmark |   PASS   | 2 ms | n/a | +|Format | Viz create canvas |   PASS   | 3 ms | n/a | +|Format | Viz frame diff |   PASS   | 2 ms | n/a | +|Format | Viz layout |   PASS   | 14 ms | n/a | +|Format | Word wrap |   PASS   | 14 ms | n/a | +|HTTP | HTTP changing user agent |   PASS   | 388 ms | n/a | +|HTTP | HTTP plugin basic functionality |   PASS   | 55 ms | n/a | +|HTTP | HTTP plugin test basic auth |   PASS   | 839 ms | n/a | +|HTTP | HTTP plugin web socket client |   PASS   | 2 seconds, 652 ms | n/a | +|IO | IO Test Gzip Native to Byte array |   PASS   | 26 ms | n/a | +|IO | IO Test JSON |   PASS   | 3 ms | n/a | +|IO | IO Test Stream JSON |   PASS   | 30 ms | n/a | +|IO | IO Test TAR functionality |   PASS   | 4 seconds, 423 ms | n/a | +|IO | IO Test binary file detection |   PASS   | 11 ms | n/a | +|IO | IO Test copy streams |   PASS   | 174 ms | n/a | |IO | IO Test copy/move/delete file |   PASS   | 6 ms | n/a | -|IO | IO Test read/writeFileStream |   PASS   | 6 ms | n/a | -|IO | IO Test read/writeFileStream NIO |   PASS   | 6 ms | n/a | +|IO | IO Test read/writeFileStream |   PASS   | 3 ms | n/a | +|IO | IO Test read/writeFileStream NIO |   PASS   | 4 ms | n/a | |IO | IO Test read/writeFileStreamBytes |   PASS   | 2 ms | n/a | -|IO | IO Test read/writeFileStreamBytes NIO |   PASS   | 1 ms | n/a | -|JMX | JMX test |   PASS   | 562 ms | n/a | -|Java | Java ASym Cipher |   PASS   | 172 ms | n/a | -|Java | Java Cipher |   PASS   | 278 ms | n/a | -|Net | Get Actual Time (default) |   PASS   | 37 ms | n/a | -|Net | Get Actual Time (server and timeout) |   PASS   | 43 ms | n/a | -|Net | Get Actual Time (with server) |   PASS   | 20 ms | n/a | -|Net | Get Actual Time (with timeout) |   PASS   | 20 ms | n/a | +|IO | IO Test read/writeFileStreamBytes NIO |   PASS   | 2 ms | n/a | +|JMX | JMX test |   PASS   | 920 ms | n/a | +|Java | Java ASym Cipher |   PASS   | 133 ms | n/a | +|Java | Java Cipher |   PASS   | 269 ms | n/a | +|MCP | OAuth authorization URL includes resource and PKCE |   PASS   | 36 ms | n/a | +|MCP | OAuth authorization code token exchange includes resource and verifier |   PASS   | 73 ms | n/a | +|MCP | OAuth discovery with client credentials |   PASS   | 146 ms | n/a | +|MCP | oJob tplDesc renders all tool metadata strings |   PASS   | 311 ms | n/a | +|Net | Get Actual Time (default) |   PASS   | 226 ms | n/a | +|Net | Get Actual Time (server and timeout) |   PASS   | 19 ms | n/a | +|Net | Get Actual Time (with server) |   PASS   | 173 ms | n/a | +|Net | Get Actual Time (with timeout) |   PASS   | 173 ms | n/a | |Net | Load Net |   PASS   | ~0 ms | n/a | |Obj | Array to object conversion |   PASS   | 1 ms | n/a | -|Obj | DB ResultSet to object |   PASS   | 1 second, 421 ms | n/a | -|Obj | Filter |   PASS   | 9 ms | n/a | +|Obj | DB ResultSet to object |   PASS   | 1 second, 446 ms | n/a | +|Obj | Filter |   PASS   | 21 ms | n/a | |Obj | Flat map |   PASS   | 13 ms | n/a | -|Obj | Flatten |   PASS   | 211 ms | n/a | -|Obj | Generate One of |   PASS   | 42 ms | n/a | -|Obj | Generate One of Fn |   PASS   | 15 ms | n/a | +|Obj | Flatten |   PASS   | 205 ms | n/a | +|Obj | Generate One of |   PASS   | 25 ms | n/a | +|Obj | Generate One of Fn |   PASS   | 12 ms | n/a | |Obj | Get Path |   PASS   | 1 ms | n/a | -|Obj | Object fuzzy search |   PASS   | 45 ms | n/a | -|Obj | Object pool |   PASS   | 6 seconds, 79 ms | n/a | -|Obj | Object schema validation |   PASS   | 310 ms | n/a | -|Obj | Object to array conversion |   PASS   | 2 ms | n/a | +|Obj | Object fuzzy search |   PASS   | 47 ms | n/a | +|Obj | Object pool |   PASS   | 6 seconds, 77 ms | n/a | +|Obj | Object schema validation |   PASS   | 263 ms | n/a | +|Obj | Object to array conversion |   PASS   | 1 ms | n/a | |Obj | Set Path |   PASS   | 1 ms | n/a | -|Obj | Sign object |   PASS   | 422 ms | n/a | -|Obj | Thread-safe array |   PASS   | 2 ms | n/a | +|Obj | Sign object |   PASS   | 413 ms | n/a | +|Obj | Thread-safe array |   PASS   | 4 ms | n/a | |Obj | Thread-safe array |   PASS   | ~0 ms | n/a | |Obj | Thread-safe map |   PASS   | 2 ms | n/a | -|OpenAF | Await |   PASS   | 13 ms | n/a | -|OpenAF | AwaitAll |   PASS   | 54 ms | n/a | -|OpenAF | Basic Parallel processing |   PASS   | 12 ms | n/a | -|OpenAF | Cache |   PASS   | 330 ms | n/a | -|OpenAF | Crypt |   PASS   | 10 ms | n/a | +|OpenAF | Await |   PASS   | 57 ms | n/a | +|OpenAF | AwaitAll |   PASS   | 55 ms | n/a | +|OpenAF | Basic Parallel processing |   PASS   | 10 ms | n/a | +|OpenAF | Cache |   PASS   | 321 ms | n/a | +|OpenAF | Crypt |   PASS   | 34 ms | n/a | |OpenAF | DescType |   PASS   | 2 ms | n/a | |OpenAF | Encrypt/Decrypt |   PASS   | ~0 ms | n/a | |OpenAF | Get Path |   PASS   | 1 ms | n/a | -|OpenAF | Get version |   PASS   | 2 ms | n/a | +|OpenAF | Get version |   PASS   | 1 ms | n/a | |OpenAF | IsFunctions |   PASS   | 1 ms | n/a | -|OpenAF | Java RegExp |   PASS   | 9 ms | n/a | -|OpenAF | Logs |   PASS   | 4 ms | n/a | +|OpenAF | Java RegExp |   PASS   | 4 ms | n/a | +|OpenAF | Logs |   PASS   | 2 ms | n/a | |OpenAF | Map Array |   PASS   | 1 ms | n/a | -|OpenAF | Map22Array |   PASS   | 516 ms | n/a | -|OpenAF | NDJSON |   PASS   | 4 ms | n/a | -|OpenAF | PSelect |   PASS   | 7 ms | n/a | +|OpenAF | Map22Array |   PASS   | 567 ms | n/a | +|OpenAF | NDJSON |   PASS   | 2 ms | n/a | +|OpenAF | PSelect |   PASS   | 9 ms | n/a | |OpenAF | Prints |   PASS   | 1 ms | n/a | -|OpenAF | Queue |   PASS   | 16 ms | n/a | +|OpenAF | Queue |   PASS   | 14 ms | n/a | |OpenAF | Range |   PASS   | 4 ms | n/a | |OpenAF | Retry |   PASS   | 1 ms | n/a | |OpenAF | SPrints |   PASS   | 1 ms | n/a | |OpenAF | Search Key and Values |   PASS   | 6 ms | n/a | -|OpenAF | Set Path |   PASS   | 2 ms | n/a | -|OpenAF | Test $do |   PASS   | 15 ms | n/a | -|OpenAF | Test $doAll |   PASS   | 8 ms | n/a | -|OpenAF | Test $doFirst |   PASS   | 157 ms | n/a | +|OpenAF | Set Path |   PASS   | 1 ms | n/a | +|OpenAF | Test $do |   PASS   | 11 ms | n/a | +|OpenAF | Test $doAll |   PASS   | 5 ms | n/a | +|OpenAF | Test $doFirst |   PASS   | 153 ms | n/a | |OpenAF | Test AF Parse |   PASS   | 1 ms | n/a | -|OpenAF | Test BCrypt |   PASS   | 1 second, 542 ms | n/a | +|OpenAF | Test BCrypt |   PASS   | 772 ms | n/a | |OpenAF | Test Encoding |   PASS   | 1 ms | n/a | |OpenAF | Test Envs |   PASS   | 1 ms | n/a | -|OpenAF | Test FLock |   PASS   | 5 ms | n/a | +|OpenAF | Test FLock |   PASS   | 6 ms | n/a | |OpenAF | Test Format Conversion to/from base64 |   PASS   | ~0 ms | n/a | |OpenAF | Test Format Conversion to/from bytes |   PASS   | ~0 ms | n/a | |OpenAF | Test GetSet |   PASS   | 3 ms | n/a | -|OpenAF | Test Lock |   PASS   | 1 ms | n/a | +|OpenAF | Test Lock |   PASS   | 2 ms | n/a | |OpenAF | Test Merge |   PASS   | ~0 ms | n/a | |OpenAF | Test Object Compression |   PASS   | ~0 ms | n/a | -|OpenAF | Test Path |   PASS   | 115 ms | n/a | -|OpenAF | Test Rest |   PASS   | 296 ms | n/a | +|OpenAF | Test Path |   PASS   | 202 ms | n/a | +|OpenAF | Test Rest |   PASS   | 358 ms | n/a | |OpenAF | Test SHA1 |   PASS   | ~0 ms | n/a | |OpenAF | Test SHA256 |   PASS   | ~0 ms | n/a | |OpenAF | Test SHA512 |   PASS   | ~0 ms | n/a | |OpenAF | Test Scope Ids |   PASS   | 1 ms | n/a | -|OpenAF | Test Shell |   PASS   | 3 ms | n/a | +|OpenAF | Test Shell |   PASS   | 2 ms | n/a | |OpenAF | Test Shell with Map |   PASS   | 2 ms | n/a | -|OpenAF | Test Stream conversions |   PASS   | 1 ms | n/a | +|OpenAF | Test Stream conversions |   PASS   | ~0 ms | n/a | |OpenAF | Test Void shortcut |   PASS   | ~0 ms | n/a | -|OpenAF | Test clone |   PASS   | 2 ms | n/a | -|OpenAF | Test pForEach |   PASS   | 135 ms | n/a | -|OpenAF | Thread box |   PASS   | 306 ms | n/a | +|OpenAF | Test clone |   PASS   | ~0 ms | n/a | +|OpenAF | Test pForEach |   PASS   | 155 ms | n/a | +|OpenAF | Thread box |   PASS   | 308 ms | n/a | |OpenAF | Two factor authentication |   PASS   | 2 ms | n/a | -|OpenAF | XML2And4Obj |   PASS   | 211 ms | n/a | -|OpenAF | YAML |   PASS   | 5 ms | n/a | -|Sec | Sec basic functionality |   PASS   | 15 ms | n/a | -|Sec | Sec function functionality |   PASS   | 458 ms | n/a | -|Sec | Sec object functionality |   PASS   | 885 ms | n/a | -|Server | Auth |   PASS   | 6 seconds, 18 ms | n/a | -|Server | AuthApp |   PASS   | 163 ms | n/a | -|Server | HTTP server |   PASS   | 110 ms | n/a | -|Server | HTTP server Java |   PASS   | 20 ms | n/a | +|OpenAF | XML2And4Obj |   PASS   | 302 ms | n/a | +|OpenAF | YAML |   PASS   | 6 ms | n/a | +|Sec | Sec basic functionality |   PASS   | 14 ms | n/a | +|Sec | Sec function functionality |   PASS   | 327 ms | n/a | +|Sec | Sec object functionality |   PASS   | 959 ms | n/a | +|Server | Auth |   PASS   | 6 seconds, 16 ms | n/a | +|Server | AuthApp |   PASS   | 162 ms | n/a | +|Server | CheckIn |   PASS   | 3 ms | n/a | +|Server | HTTP server |   PASS   | 140 ms | n/a | +|Server | HTTP server Java |   PASS   | 22 ms | n/a | |Server | HTTP server NWU2 |   PASS   | 76 ms | n/a | -|Server | Locks |   PASS   | 2 seconds, 22 ms | n/a | -|Server | Queue |   PASS   | 4 seconds, 669 ms | n/a | -|Server | REST server |   PASS   | 102 ms | n/a | -|Server | REST server Java |   PASS   | 177 ms | n/a | -|Server | REST server NWU2 |   PASS   | 66 ms | n/a | -|Server | REST server simple |   PASS   | 32 ms | n/a | +|Server | HTTP server prefix |   FAIL   | 20 ms | (nwu) Problem stripping HTTPD prefix before route handlers. (got { "responseCode": 200, "contentType": "text/plain", "response": "/normal" } but expected "/normal") | +|Server | HTTP server prefix Java |   FAIL   | 10 ms | (java) Problem stripping HTTPD prefix before route handlers. (got { "responseCode": 200, "contentType": "text/plain", "response": "/normal" } but expected "/normal") | +|Server | HTTP server prefix NWU2 |   FAIL   | 19 ms | (nwu2) Problem stripping HTTPD prefix before route handlers. (got { "responseCode": 200, "contentType": "text/plain", "response": "/normal" } but expected "/normal") | +|Server | HTTP server prefix helpers |   PASS   | 4 ms | n/a | +|Server | Locks |   PASS   | 2 seconds, 15 ms | n/a | +|Server | Queue |   PASS   | 5 seconds, 74 ms | n/a | +|Server | REST server |   PASS   | 66 ms | n/a | +|Server | REST server Java |   PASS   | 288 ms | n/a | +|Server | REST server NWU2 |   PASS   | 48 ms | n/a | +|Server | REST server simple |   PASS   | 37 ms | n/a | |Server | REST server simple Java |   PASS   | 22 ms | n/a | -|Server | REST server simple NWU2 |   PASS   | 28 ms | n/a | -|Server | Scheduler |   PASS   | 16 seconds, 24 ms | n/a | +|Server | REST server simple NWU2 |   PASS   | 34 ms | n/a | +|Server | Scheduler |   PASS   | 16 seconds, 26 ms | n/a | |Template | Load Template |   PASS   | ~0 ms | n/a | -|Template | Test Markdown to HTML |   PASS   | 654 ms | n/a | -|Template | Test conditional helpers |   PASS   | 224 ms | n/a | -|Template | Test format helpers |   PASS   | 63 ms | n/a | -|Template | Test openaf helpers |   PASS   | 127 ms | n/a | -|Template | Test partial helpers |   PASS   | 38 ms | n/a | -|Template | Test simple template |   PASS   | 53 ms | n/a | -|ZIP | ZIP basic functionality |   PASS   | 1 second, 462 ms | n/a | -|ZIP | ZIP streaming functionality |   PASS   | 1 second, 496 ms | n/a | -|oDoc | Test access to oDoc |   PASS   | 1 second, 4 ms | n/a | -|oJob | oJob |   PASS   | 1 second, 672 ms | n/a | -|oJob | oJobArgsMultipleLevels |   PASS   | 1 second, 980 ms | n/a | -|oJob | oJobChecks |   PASS   | 1 second, 671 ms | n/a | -|oJob | oJobInitArray |   PASS   | 4 seconds, 100 ms | n/a | -|oJob | oJobPass |   PASS   | 1 second, 813 ms | n/a | -|oJob | oJobShortcutOutput |   PASS   | 1 second, 888 ms | n/a | -|oJob | oJobShortcuts |   PASS   | 1 second, 780 ms | n/a | +|Template | Test Markdown to HTML |   PASS   | 622 ms | n/a | +|Template | Test Markdown to HTML prefix |   PASS   | 166 ms | n/a | +|Template | Test conditional helpers |   PASS   | 189 ms | n/a | +|Template | Test format helpers |   PASS   | 54 ms | n/a | +|Template | Test openaf helpers |   PASS   | 94 ms | n/a | +|Template | Test partial helpers |   PASS   | 41 ms | n/a | +|Template | Test simple template |   PASS   | 43 ms | n/a | +|ZIP | ZIP basic functionality |   PASS   | 1 second, 517 ms | n/a | +|ZIP | ZIP streaming functionality |   PASS   | 1 second, 532 ms | n/a | +|oDoc | Test access to oDoc |   PASS   | 925 ms | n/a | +|oJob | oJob |   PASS   | 1 second, 895 ms | n/a | +|oJob | oJobArgsMultipleLevels |   PASS   | 1 second, 994 ms | n/a | +|oJob | oJobChecks |   PASS   | 1 second, 854 ms | n/a | +|oJob | oJobEncryptedJSON |   PASS   | 1 second, 725 ms | n/a | +|oJob | oJobEncryptedYAML |   PASS   | 1 second, 834 ms | n/a | +|oJob | oJobInitArray |   PASS   | 4 seconds, 209 ms | n/a | +|oJob | oJobPass |   PASS   | 1 second, 985 ms | n/a | +|oJob | oJobShortcutOutput |   PASS   | 1 second, 910 ms | n/a | +|oJob | oJobShortcuts |   PASS   | 1 second, 948 ms | n/a |