mirror of
https://github.com/HeyPuter/puter.git
synced 2026-09-11 15:55:44 +00:00
Review of the previous change found three of its claims unmet.
The image-generation crash it reported fixed is still reachable. The assert was
scattered across three helpers, and Gemini and OpenAI call `isHttpUrl` directly
on `input_images` without going through any of them — so two of seven providers
still 500 on a non-string. `isHttpUrl` now refuses a non-string itself, and the
shape is settled once in `ImageGenerationDriver.generate`, where the driver call
arrives, rather than per helper. That also covers `input_images` that isn't an
array, which produced a different crash per provider.
The sixth case in the ticket, previously unlocated, is
`Messages.js` reading `tool_call.function.name` with no guard — reachable with
`{"messages":[{"role":"assistant","tool_calls":[{"id":"x"}]}]}`. Guarded, along
with the same shape in `make_claude_tools`: a TypeError there carries no status,
so the retry loop reads it as a provider failure and marks the route unhealthy
for every caller.
`#hardExpiryFromExpiresIn` returning null for a bad type moved the failure past
the session INSERT, leaving an orphaned non-expiring row and still answering
500. Reverted; the controller guard is the fix, now covering fractions,
negatives and unparseable durations rather than only wrong types.
Also: the batch write handlers check that the body is an array but not what is
in it, so a null element 500s the same way; `#requireObjectBody` accepted an
array despite its name; `handleCreateAccessToken` destructured a body that may
be absent; and two AGPL notices had been rewrapped with a Markdown link.
159 lines
4.4 KiB
JavaScript
159 lines
4.4 KiB
JavaScript
/*
|
|
* Copyright (C) 2024-present Puter Technologies Inc.
|
|
*
|
|
* This file is part of Puter.
|
|
*
|
|
* Puter is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published
|
|
* by the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import { HttpError } from '@heyputer/backend/src/core/http';
|
|
|
|
export const normalize_json_schema = (schema) => {
|
|
if (!schema) return schema;
|
|
|
|
if (schema.type === 'object') {
|
|
if (!schema.properties) {
|
|
return schema;
|
|
}
|
|
|
|
const keys = Object.keys(schema.properties);
|
|
for (const key of keys) {
|
|
schema.properties[key] = normalize_json_schema(
|
|
schema.properties[key],
|
|
);
|
|
}
|
|
}
|
|
|
|
if (schema.type === 'array') {
|
|
if (!schema.items) {
|
|
schema.items = {};
|
|
} else {
|
|
schema.items = normalize_json_schema(schema.items);
|
|
}
|
|
}
|
|
|
|
return schema;
|
|
};
|
|
|
|
/**
|
|
* Normalizes the 'tools' object in-place.
|
|
*
|
|
* This function will accept an array of tools provided by the user, and produce
|
|
* a normalized object that can then be converted to the apprpriate
|
|
* representation for another service.
|
|
*
|
|
* We will accept conventions from either service that a user might expect to
|
|
* work, prioritizing the OpenAI convention when conflicting conventions are
|
|
* present.
|
|
*
|
|
* @param {any} tools
|
|
*/
|
|
export const normalize_tools_object = (tools) => {
|
|
for (let i = 0; i < tools.length; i++) {
|
|
const tool = tools[i];
|
|
|
|
if (tool.type === 'web_search') {
|
|
// OpenAI Responses specific
|
|
continue;
|
|
}
|
|
let normalized_tool = {};
|
|
|
|
const normalize_function = (fn) => {
|
|
const normal_fn = {};
|
|
let parameters = fn.parameters || fn.input_schema;
|
|
|
|
if (!parameters || typeof parameters !== 'object') {
|
|
parameters = { type: 'object' };
|
|
} else if (!parameters.type) {
|
|
parameters.type = 'object';
|
|
}
|
|
|
|
normal_fn.parameters = parameters;
|
|
|
|
if (parameters.properties) {
|
|
parameters = normalize_json_schema(parameters);
|
|
}
|
|
|
|
if (fn.name) {
|
|
normal_fn.name = fn.name;
|
|
}
|
|
|
|
if (fn.description) {
|
|
normal_fn.description = fn.description;
|
|
}
|
|
|
|
return normal_fn;
|
|
};
|
|
|
|
if (tool.input_schema) {
|
|
normalized_tool = {
|
|
type: 'function',
|
|
function: normalize_function(tool),
|
|
};
|
|
} else if (tool.type === 'function') {
|
|
normalized_tool = {
|
|
type: 'function',
|
|
function: normalize_function(tool.function || tool),
|
|
};
|
|
} else {
|
|
normalized_tool = {
|
|
type: 'function',
|
|
function: normalize_function(tool),
|
|
};
|
|
}
|
|
|
|
tools[i] = normalized_tool;
|
|
}
|
|
return tools;
|
|
};
|
|
|
|
/**
|
|
* This function will convert a normalized tools object to the format expected
|
|
* by OpenAI.
|
|
*
|
|
* @param {any} tools
|
|
* @returns
|
|
*/
|
|
export const make_openai_tools = (tools) => {
|
|
return tools;
|
|
};
|
|
|
|
/**
|
|
* This function will convert a normalized tools object to the format expected
|
|
* by Claude.
|
|
*
|
|
* @param {any} tools
|
|
* @returns
|
|
*/
|
|
export const make_claude_tools = (tools) => {
|
|
if (!tools) return undefined;
|
|
return tools.map((tool) => {
|
|
// A TypeError here carries no status, so it is read as a provider
|
|
// failure and marks the route unhealthy for everyone.
|
|
if (!tool?.function) {
|
|
throw new HttpError(
|
|
400,
|
|
"each tool must have a 'function' property",
|
|
{ legacyCode: 'bad_request' },
|
|
);
|
|
}
|
|
const { name, description, parameters } = tool.function;
|
|
return {
|
|
name,
|
|
description,
|
|
input_schema: parameters,
|
|
};
|
|
});
|
|
};
|