refactor: Remove project rules

This commit is contained in:
Petr Mironychev
2026-06-11 13:36:23 +02:00
parent 2c9475cddf
commit 05fe38e289
45 changed files with 1333 additions and 299 deletions

View File

@@ -55,6 +55,8 @@ Agent::Agent(AgentConfig config, Providers::Provider *providerOwned, QObject *pa
return;
}
m_provider->setParent(this);
m_provider->setPromptCaching(
m_config.cachePrompt, m_config.cacheTtl == QLatin1StringView{"1h"});
QString tmplErr;
m_promptTemplate = JsonPromptTemplate::fromConfig(m_config, &tmplErr);

View File

@@ -39,6 +39,8 @@ struct AgentConfig
bool enableThinking = false;
bool enableTools = false;
bool cachePrompt = false;
QString cacheTtl;
QJsonObject body;
QString extendsName;

View File

@@ -194,7 +194,7 @@ Agent *AgentFactory::createFromFile(
{
QString parseErr;
QStringList warnings;
auto cfgOpt = Agents::AgentLoader::parseFile(tomlPath, &parseErr, &warnings);
auto cfgOpt = Agents::AgentLoader::parseFile(tomlPath, agentQrcPrefix(), &parseErr, &warnings);
if (!cfgOpt) {
if (errorOut) *errorOut = parseErr;
return nullptr;

View File

@@ -6,6 +6,7 @@
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QHash>
#include <QJsonArray>
#include <QJsonDocument>
@@ -123,6 +124,8 @@ AgentConfig configFromMerged(const QJsonObject &obj)
cfg.systemPrompt = obj.value("system_prompt").toString();
cfg.enableThinking = obj.value("enable_thinking").toBool(false);
cfg.enableTools = obj.value("enable_tools").toBool(false);
cfg.cachePrompt = obj.value("cache_prompt").toBool(false);
cfg.cacheTtl = obj.value("cache_ttl").toString();
cfg.tags = stringArray(obj.value("tags"));
const QJsonObject matchObj = obj.value("match").toObject();
@@ -147,6 +150,34 @@ struct RawEntry
constexpr int kMaxExtendsDepth = 32;
void scanDir(
const QString &dir,
bool isUserLayer,
QHash<QString, RawEntry> &raw,
QStringList &errors)
{
if (dir.isEmpty()) return;
QDir d(dir);
if (!d.exists()) return;
const QStringList files = d.entryList({"*.toml"}, QDir::Files);
for (const QString &fname : files) {
const QString fullPath = d.filePath(fname);
QString err;
auto objOpt = parseTomlFile(fullPath, &err);
if (!objOpt) {
errors.append(err);
continue;
}
const QString name = objOpt->value("name").toString();
if (name.isEmpty()) {
errors.append(QStringLiteral("Agent at %1 has no 'name'").arg(fullPath));
continue;
}
const bool overrides = isUserLayer && raw.contains(name);
raw.insert(name, {*objOpt, fullPath, overrides});
}
}
QJsonObject resolveExtends(
const QString &name,
const QHash<QString, RawEntry> &raw,
@@ -190,12 +221,47 @@ QJsonObject resolveExtends(
} // namespace
std::optional<AgentConfig> AgentLoader::parseFile(
const QString &path, QString *error, QStringList * /*warnings*/)
const QString &path,
const QString &qrcPrefix,
QString *error,
QStringList * /*warnings*/)
{
auto objOpt = parseTomlFile(path, error);
if (!objOpt) return std::nullopt;
AgentConfig cfg = configFromMerged(*objOpt);
const QString name = objOpt->value("name").toString();
if (name.isEmpty()) {
if (error) *error = QStringLiteral("Agent at %1 has no 'name'").arg(path);
return std::nullopt;
}
QHash<QString, RawEntry> raw;
QStringList scanErrors;
scanDir(qrcPrefix, /*isUserLayer=*/false, raw, scanErrors);
scanDir(QFileInfo(path).absolutePath(), /*isUserLayer=*/true, raw, scanErrors);
raw.insert(name, {*objOpt, path, raw.contains(name)});
QSet<QString> visiting;
QStringList resolveErrors;
const QJsonObject merged = resolveExtends(name, raw, visiting, resolveErrors);
if (!resolveErrors.isEmpty() || merged.isEmpty()) {
if (error) {
*error = resolveErrors.isEmpty()
? QStringLiteral("Agent '%1' resolved to an empty config").arg(name)
: resolveErrors.join(QStringLiteral("; "));
}
return std::nullopt;
}
AgentConfig cfg = configFromMerged(merged);
cfg.sourcePath = path;
if (cfg.abstract) {
if (error) {
*error = QStringLiteral("Agent '%1' is abstract — extend it instead of "
"loading it directly").arg(name);
}
return std::nullopt;
}
return cfg;
}
@@ -204,31 +270,8 @@ AgentLoader::LoadResult AgentLoader::load(const QString &qrcPrefix, const QStrin
LoadResult result;
QHash<QString, RawEntry> raw;
auto scan = [&](const QString &dir, bool isUserLayer) {
if (dir.isEmpty()) return;
QDir d(dir);
if (!d.exists()) return;
const QStringList files = d.entryList({"*.toml"}, QDir::Files);
for (const QString &fname : files) {
const QString fullPath = d.filePath(fname);
QString err;
auto objOpt = parseTomlFile(fullPath, &err);
if (!objOpt) {
result.errors.append(err);
continue;
}
const QString name = objOpt->value("name").toString();
if (name.isEmpty()) {
result.errors.append(QStringLiteral("Agent at %1 has no 'name'").arg(fullPath));
continue;
}
const bool overrides = isUserLayer && raw.contains(name);
raw.insert(name, {*objOpt, fullPath, overrides});
}
};
scan(qrcPrefix, /*isUserLayer=*/false);
scan(userDir, /*isUserLayer=*/true);
scanDir(qrcPrefix, /*isUserLayer=*/false, raw, result.errors);
scanDir(userDir, /*isUserLayer=*/true, raw, result.errors);
for (auto it = raw.constBegin(); it != raw.constEnd(); ++it) {
const QString &name = it.key();

View File

@@ -25,7 +25,10 @@ public:
static LoadResult load(const QString &qrcPrefix, const QString &userDir);
static std::optional<AgentConfig> parseFile(
const QString &path, QString *error, QStringList *warnings = nullptr);
const QString &path,
const QString &qrcPrefix,
QString *error,
QStringList *warnings = nullptr);
};
} // namespace QodeAssist::Agents

View File

@@ -7,6 +7,7 @@ abstract = true
provider_instance = "Claude"
endpoint = "/v1/messages"
enable_tools = true
cache_prompt = true
tags = ["chat", "claude", "anthropic", "cloud"]
system_prompt = """{{ agent_role() }}"""

View File

@@ -3,8 +3,17 @@
"role": {{ tojson(msg.role) }},
"content": [
{% for b in msg.content_blocks %}
{% if b.type == "image" %}{% include "partials/anthropic_image.jinja" %}
{% else %}{{ tojson(b) }},
{% if b.type == "text" %}
{ "type": "text", "text": {{ tojson(b.text) }} },
{% else if b.type == "thinking" %}
{ "type": "thinking", "thinking": {{ tojson(b.thinking) }}, "signature": {{ tojson(b.signature) }} },
{% else if b.type == "redacted_thinking" %}
{ "type": "redacted_thinking", "data": {{ tojson(b.data) }} },
{% else if b.type == "tool_use" %}
{ "type": "tool_use", "id": {{ tojson(b.id) }}, "name": {{ tojson(b.name) }}, "input": {{ tojson(b.input) }} },
{% else if b.type == "tool_result" %}
{ "type": "tool_result", "tool_use_id": {{ tojson(b.tool_use_id) }}, "content": {{ tojson(b.content) }} },
{% else if b.type == "image" %}{% include "partials/anthropic_image.jinja" %}
{% endif %}
{% endfor %}
]

View File

@@ -2,15 +2,36 @@
{ "role": "system", "content": {{ tojson(ctx.system_prompt) }} },
{% endif %}
{% for msg in ctx.history %}
{
"role": {{ tojson(msg.role) }},
"content": {{ tojson(msg.content) }}
{% if existsIn(msg, "images") %}
, "images": [
{% for img in msg.images %}
{{ tojson(img.data) }},
{% set tcalls = filter_by_type(msg.content_blocks, "tool_use") %}
{% set tresults = filter_by_type(msg.content_blocks, "tool_result") %}
{% if length(tresults) > 0 %}
{% for b in tresults %}
{
"role": "tool",
"content": {{ tojson(b.content) }}
{% if b.name != "" %}
, "tool_name": {{ tojson(b.name) }}
{% endif %}
},
{% endfor %}
]
{% else %}
{
"role": {{ tojson(msg.role) }},
"content": {{ tojson(msg.content) }}
{% if length(tcalls) > 0 %}
, "tool_calls": [
{% for b in tcalls %}
{ "type": "function", "function": { "name": {{ tojson(b.name) }}, "arguments": {{ tojson(b.input) }} } },
{% endfor %}
]
{% endif %}
{% if existsIn(msg, "images") %}
, "images": [
{% for img in msg.images %}
{{ tojson(img.data) }},
{% endfor %}
]
{% endif %}
},
{% endif %}
},
{% endfor %}

View File

@@ -1,7 +1,7 @@
{% set tcalls = filter_by_type(msg.content_blocks, "tool_use") %}
{
"role": "assistant",
"content": {{ tojson(msg.content) }}
"content": {% if msg.content != "" %}{{ tojson(msg.content) }}{% else %}null{% endif %}
{% if length(tcalls) > 0 %}
, "tool_calls": [
{% for b in tcalls %}

View File

@@ -1,11 +1,12 @@
[
{ "type": "text", "text": {{ tojson(msg.content) }} }
{% if msg.content != "" %}
{ "type": "text", "text": {{ tojson(msg.content) }} },
{% endif %}
{% for img in msg.images %}
,
{% if img.is_url %}
{ "type": "image_url", "image_url": { "url": {{ tojson(img.data) }} } }
{ "type": "image_url", "image_url": { "url": {{ tojson(img.data) }} } },
{% else %}
{ "type": "image_url", "image_url": { "url": "data:{{ img.media_type }};base64,{{ img.data }}" } }
{ "type": "image_url", "image_url": { "url": "data:{{ img.media_type }};base64,{{ img.data }}" } },
{% endif %}
{% endfor %}
]