Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/iac_code/agent/system_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,10 @@ def _build_tools_section() -> str:
"- Read files before modifying them.\n"
"- Use EditFile for surgical edits to existing files.\n"
"- Use WriteFile only for creating new files or complete rewrites.\n"
"- If a tool call fails, do not retry the same call. Adjust your approach."
"- If a tool call fails, do not retry the same call. Adjust your approach.\n"
"- Never resend an equivalent request after a failure: an error text is a symptom, not a diagnosis. "
"Identify the root cause first, then either switch to a path that does not depend on the failing "
"precondition, or state an explicit conclusion about why the goal cannot be reached."
)


Expand Down
26 changes: 26 additions & 0 deletions src/iac_code/i18n/locales/de/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -5480,6 +5480,15 @@ msgstr ""
"Ungültige Eingabe für Tool „{tool_name}“: {error}. Geben Sie alle "
"erforderlichen Parameter gemäß dem Tool-Schema an."

#: src/iac_code/tools/tool_executor.py
#, python-brace-format
msgid ""
"Tool '{tool_name}' already failed terminally ({signature}) for this exact"
" request. Resending an equivalent request will fail the same way. "
"Diagnose the root cause, then switch to a different request or path, or "
"report an explicit conclusion instead."
msgstr "Das Tool „{tool_name}“ ist für genau diese Anfrage bereits endgültig fehlgeschlagen ({signature}). Eine gleichwertige Anfrage schlägt genauso fehl. Ermitteln Sie die Ursache und wechseln Sie dann die Anfrage oder den Pfad, oder nennen Sie eine ausdrückliche Schlussfolgerung."

#: src/iac_code/tools/tool_executor.py
#, python-brace-format
msgid "Tool '{tool_name}' timed out after {timeout}s"
Expand Down Expand Up @@ -6528,6 +6537,23 @@ msgstr ""
"max_response_bytes der Alibaba-Cloud-API {operation} muss zwischen 1 und "
"16777216 liegen."

#: src/iac_code/tools/cloud/aliyun/public_errors.py
#, python-brace-format
msgid ""
"Alibaba Cloud API {operation} rejected the request with HTTP {status} and"
" error code {code}. An equivalent request will be rejected again; fix the"
" request or switch to an API path that does not depend on the rejected "
"input."
msgstr "Die Alibaba Cloud API {operation} hat die Anfrage mit HTTP {status} und Fehlercode {code} abgelehnt. Eine gleichwertige Anfrage wird erneut abgelehnt; korrigieren Sie die Anfrage oder wechseln Sie zu einem API-Pfad, der nicht von der abgelehnten Eingabe abhängt."

#: src/iac_code/tools/cloud/aliyun/public_errors.py
#, python-brace-format
msgid ""
"Alibaba Cloud API {operation} rejected the request with HTTP {status}. An"
" equivalent request will be rejected again; fix the request or switch to "
"an API path that does not depend on the rejected input."
msgstr "Die Alibaba Cloud API {operation} hat die Anfrage mit HTTP {status} abgelehnt. Eine gleichwertige Anfrage wird erneut abgelehnt; korrigieren Sie die Anfrage oder wechseln Sie zu einem API-Pfad, der nicht von der abgelehnten Eingabe abhängt."

#: src/iac_code/tools/cloud/aliyun/public_errors.py
#, python-brace-format
msgid ""
Expand Down
26 changes: 26 additions & 0 deletions src/iac_code/i18n/locales/es/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -5443,6 +5443,15 @@ msgstr ""
" todos los parámetros obligatorios definidos en el esquema de la "
"herramienta."

#: src/iac_code/tools/tool_executor.py
#, python-brace-format
msgid ""
"Tool '{tool_name}' already failed terminally ({signature}) for this exact"
" request. Resending an equivalent request will fail the same way. "
"Diagnose the root cause, then switch to a different request or path, or "
"report an explicit conclusion instead."
msgstr "La herramienta «{tool_name}» ya falló de forma definitiva ({signature}) para esta misma solicitud. Reenviar una solicitud equivalente fallará igual. Diagnostique la causa raíz y luego cambie de solicitud o de ruta, o informe una conclusión explícita."

#: src/iac_code/tools/tool_executor.py
#, python-brace-format
msgid "Tool '{tool_name}' timed out after {timeout}s"
Expand Down Expand Up @@ -6496,6 +6505,23 @@ msgstr ""
"El max_response_bytes de la API de Alibaba Cloud {operation} debe estar "
"entre 1 y 16777216."

#: src/iac_code/tools/cloud/aliyun/public_errors.py
#, python-brace-format
msgid ""
"Alibaba Cloud API {operation} rejected the request with HTTP {status} and"
" error code {code}. An equivalent request will be rejected again; fix the"
" request or switch to an API path that does not depend on the rejected "
"input."
msgstr "La API de Alibaba Cloud {operation} rechazó la solicitud con HTTP {status} y el código de error {code}. Una solicitud equivalente será rechazada de nuevo; corrija la solicitud o cambie a una ruta de API que no dependa de la entrada rechazada."

#: src/iac_code/tools/cloud/aliyun/public_errors.py
#, python-brace-format
msgid ""
"Alibaba Cloud API {operation} rejected the request with HTTP {status}. An"
" equivalent request will be rejected again; fix the request or switch to "
"an API path that does not depend on the rejected input."
msgstr "La API de Alibaba Cloud {operation} rechazó la solicitud con HTTP {status}. Una solicitud equivalente será rechazada de nuevo; corrija la solicitud o cambie a una ruta de API que no dependa de la entrada rechazada."

#: src/iac_code/tools/cloud/aliyun/public_errors.py
#, python-brace-format
msgid ""
Expand Down
26 changes: 26 additions & 0 deletions src/iac_code/i18n/locales/fr/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -5460,6 +5460,15 @@ msgstr ""
"Entrée non valide pour l’outil « {tool_name} » : {error}. Fournissez tous"
" les paramètres requis définis dans le schéma de l’outil."

#: src/iac_code/tools/tool_executor.py
#, python-brace-format
msgid ""
"Tool '{tool_name}' already failed terminally ({signature}) for this exact"
" request. Resending an equivalent request will fail the same way. "
"Diagnose the root cause, then switch to a different request or path, or "
"report an explicit conclusion instead."
msgstr "L'outil « {tool_name} » a déjà échoué définitivement ({signature}) pour cette requête exacte. Renvoyer une requête équivalente échouera de la même manière. Diagnostiquez la cause racine, puis changez de requête ou de chemin, ou formulez une conclusion explicite."

#: src/iac_code/tools/tool_executor.py
#, python-brace-format
msgid "Tool '{tool_name}' timed out after {timeout}s"
Expand Down Expand Up @@ -6510,6 +6519,23 @@ msgstr ""
"Le max_response_bytes de l'API Alibaba Cloud {operation} doit être "
"compris entre 1 et 16777216."

#: src/iac_code/tools/cloud/aliyun/public_errors.py
#, python-brace-format
msgid ""
"Alibaba Cloud API {operation} rejected the request with HTTP {status} and"
" error code {code}. An equivalent request will be rejected again; fix the"
" request or switch to an API path that does not depend on the rejected "
"input."
msgstr "L'API Alibaba Cloud {operation} a rejeté la requête avec le code HTTP {status} et le code d'erreur {code}. Une requête équivalente sera de nouveau rejetée ; corrigez la requête ou passez à un chemin d'API qui ne dépend pas de l'entrée rejetée."

#: src/iac_code/tools/cloud/aliyun/public_errors.py
#, python-brace-format
msgid ""
"Alibaba Cloud API {operation} rejected the request with HTTP {status}. An"
" equivalent request will be rejected again; fix the request or switch to "
"an API path that does not depend on the rejected input."
msgstr "L'API Alibaba Cloud {operation} a rejeté la requête avec le code HTTP {status}. Une requête équivalente sera de nouveau rejetée ; corrigez la requête ou passez à un chemin d'API qui ne dépend pas de l'entrée rejetée."

#: src/iac_code/tools/cloud/aliyun/public_errors.py
#, python-brace-format
msgid ""
Expand Down
26 changes: 26 additions & 0 deletions src/iac_code/i18n/locales/ja/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -5172,6 +5172,15 @@ msgid ""
"required parameters as defined in the tool schema."
msgstr "ツール「{tool_name}」への入力が無効です: {error}。ツールスキーマで定義されたすべての必須パラメーターを指定してください。"

#: src/iac_code/tools/tool_executor.py
#, python-brace-format
msgid ""
"Tool '{tool_name}' already failed terminally ({signature}) for this exact"
" request. Resending an equivalent request will fail the same way. "
"Diagnose the root cause, then switch to a different request or path, or "
"report an explicit conclusion instead."
msgstr "ツール「{tool_name}」はこのリクエストで既に最終的な失敗({signature})となりました。同等のリクエストを再送しても同じように失敗します。根本原因を特定し、別のリクエストや経路に切り替えるか、明確な結論を示してください。"

#: src/iac_code/tools/tool_executor.py
#, python-brace-format
msgid "Tool '{tool_name}' timed out after {timeout}s"
Expand Down Expand Up @@ -6161,6 +6170,23 @@ msgstr ""
"Alibaba Cloud API {operation} の max_response_bytes は 1 から 16777216 "
"の範囲で指定してください。"

#: src/iac_code/tools/cloud/aliyun/public_errors.py
#, python-brace-format
msgid ""
"Alibaba Cloud API {operation} rejected the request with HTTP {status} and"
" error code {code}. An equivalent request will be rejected again; fix the"
" request or switch to an API path that does not depend on the rejected "
"input."
msgstr "Alibaba Cloud API {operation} は HTTP {status}、エラーコード {code} でリクエストを拒否しました。同等のリクエストは再び拒否されます。リクエストを修正するか、拒否された入力に依存しない API 経路に切り替えてください。"

#: src/iac_code/tools/cloud/aliyun/public_errors.py
#, python-brace-format
msgid ""
"Alibaba Cloud API {operation} rejected the request with HTTP {status}. An"
" equivalent request will be rejected again; fix the request or switch to "
"an API path that does not depend on the rejected input."
msgstr "Alibaba Cloud API {operation} は HTTP {status} でリクエストを拒否しました。同等のリクエストは再び拒否されます。リクエストを修正するか、拒否された入力に依存しない API 経路に切り替えてください。"

#: src/iac_code/tools/cloud/aliyun/public_errors.py
#, python-brace-format
msgid ""
Expand Down
26 changes: 26 additions & 0 deletions src/iac_code/i18n/locales/pt/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -5406,6 +5406,15 @@ msgstr ""
"Entrada inválida para a ferramenta “{tool_name}”: {error}. Forneça todos "
"os parâmetros obrigatórios definidos no esquema da ferramenta."

#: src/iac_code/tools/tool_executor.py
#, python-brace-format
msgid ""
"Tool '{tool_name}' already failed terminally ({signature}) for this exact"
" request. Resending an equivalent request will fail the same way. "
"Diagnose the root cause, then switch to a different request or path, or "
"report an explicit conclusion instead."
msgstr "A ferramenta \"{tool_name}\" já falhou definitivamente ({signature}) para esta mesma solicitação. Reenviar uma solicitação equivalente falhará da mesma forma. Diagnostique a causa raiz e então troque de solicitação ou de caminho, ou informe uma conclusão explícita."

#: src/iac_code/tools/tool_executor.py
#, python-brace-format
msgid "Tool '{tool_name}' timed out after {timeout}s"
Expand Down Expand Up @@ -6454,6 +6463,23 @@ msgstr ""
"O max_response_bytes da API do Alibaba Cloud {operation} deve estar entre"
" 1 e 16777216."

#: src/iac_code/tools/cloud/aliyun/public_errors.py
#, python-brace-format
msgid ""
"Alibaba Cloud API {operation} rejected the request with HTTP {status} and"
" error code {code}. An equivalent request will be rejected again; fix the"
" request or switch to an API path that does not depend on the rejected "
"input."
msgstr "A API da Alibaba Cloud {operation} rejeitou a solicitação com HTTP {status} e código de erro {code}. Uma solicitação equivalente será rejeitada novamente; corrija a solicitação ou mude para um caminho de API que não dependa da entrada rejeitada."

#: src/iac_code/tools/cloud/aliyun/public_errors.py
#, python-brace-format
msgid ""
"Alibaba Cloud API {operation} rejected the request with HTTP {status}. An"
" equivalent request will be rejected again; fix the request or switch to "
"an API path that does not depend on the rejected input."
msgstr "A API da Alibaba Cloud {operation} rejeitou a solicitação com HTTP {status}. Uma solicitação equivalente será rejeitada novamente; corrija a solicitação ou mude para um caminho de API que não dependa da entrada rejeitada."

#: src/iac_code/tools/cloud/aliyun/public_errors.py
#, python-brace-format
msgid ""
Expand Down
26 changes: 26 additions & 0 deletions src/iac_code/i18n/locales/zh/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -5107,6 +5107,15 @@ msgid ""
"required parameters as defined in the tool schema."
msgstr "工具“{tool_name}”的输入无效:{error}。请按照工具架构提供所有必填参数。"

#: src/iac_code/tools/tool_executor.py
#, python-brace-format
msgid ""
"Tool '{tool_name}' already failed terminally ({signature}) for this exact"
" request. Resending an equivalent request will fail the same way. "
"Diagnose the root cause, then switch to a different request or path, or "
"report an explicit conclusion instead."
msgstr "工具“{tool_name}”对该请求已终态失败({signature})。重发等价请求只会以同样方式失败。请先定位根因,然后换用其它请求或路径,或直接给出明确结论。"

#: src/iac_code/tools/tool_executor.py
#, python-brace-format
msgid "Tool '{tool_name}' timed out after {timeout}s"
Expand Down Expand Up @@ -6022,6 +6031,23 @@ msgid ""
"16777216."
msgstr "阿里云 API {operation} 的 max_response_bytes 必须介于 1 和 16777216 之间。"

#: src/iac_code/tools/cloud/aliyun/public_errors.py
#, python-brace-format
msgid ""
"Alibaba Cloud API {operation} rejected the request with HTTP {status} and"
" error code {code}. An equivalent request will be rejected again; fix the"
" request or switch to an API path that does not depend on the rejected "
"input."
msgstr "阿里云 API {operation} 以 HTTP {status} 和错误码 {code} 拒绝了该请求。等价请求会再次被拒绝;请修正请求,或改用不依赖被拒输入的 API 路径。"

#: src/iac_code/tools/cloud/aliyun/public_errors.py
#, python-brace-format
msgid ""
"Alibaba Cloud API {operation} rejected the request with HTTP {status}. An"
" equivalent request will be rejected again; fix the request or switch to "
"an API path that does not depend on the rejected input."
msgstr "阿里云 API {operation} 以 HTTP {status} 拒绝了该请求。等价请求会再次被拒绝;请修正请求,或改用不依赖被拒输入的 API 路径。"

#: src/iac_code/tools/cloud/aliyun/public_errors.py
#, python-brace-format
msgid ""
Expand Down
25 changes: 24 additions & 1 deletion src/iac_code/tools/cloud/aliyun/aliyun_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
)
from iac_code.tools.cloud.aliyun.user_agent import build_user_agent
from iac_code.tools.cloud.base_api import BaseCloudApi
from iac_code.tools.failure_recovery import mark_terminal_failure, terminal_failure_signature
from iac_code.tools.path_safety import check_read_path_with_resolution
from iac_code.types.permissions import (
MAX_PERMISSION_AUDIT_ITEMS,
Expand Down Expand Up @@ -503,6 +504,23 @@ def _target_http_error_message(response: Any) -> str:
return f"aliyun_target_http_error:{int(response.status)}{suffix}"


def _target_terminal_failure_signature(response: Any) -> str | None:
"""Signature for statuses that reject the request shape itself, else None.

408 and 429 depend on timing rather than the request shape, so an identical
request can legitimately succeed later and must stay repeatable.
"""
status = int(response.status)
if not 400 <= status < 500 or status in {408, 429}:
return None
code = None
if isinstance(response.body, Mapping):
candidate = response.body.get("Code", response.body.get("code", response.body.get("error")))
if isinstance(candidate, str) and _TARGET_ERROR_CODE.fullmatch(candidate):
code = candidate
return terminal_failure_signature(status=status, code=code)


def _redact_target_error_detail(value: str) -> str:
redacted = _TARGET_ERROR_DETAIL_REQUEST_ID_RE.sub("<redacted>", value)
redacted = _TARGET_ERROR_DETAIL_SENSITIVE_RE.sub("<redacted>", redacted)
Expand Down Expand Up @@ -2553,6 +2571,7 @@ def observe(stage: str) -> None:
target_outcome = "target_transport_failure"
target_error_message: str | None = None
target_error_detail: str | None = None
target_terminal_signature: str | None = None
target_error: BaseException | None = None
with start_span(
Spans.ALIYUN_API_CALL,
Expand All @@ -2564,6 +2583,7 @@ def observe(stage: str) -> None:
target_outcome = "http_error"
target_error_message = _target_http_error_message(response)
target_error_detail = _target_http_error_detail(response)
target_terminal_signature = _target_terminal_failure_signature(response)
else:
target_outcome = "success"
except asyncio.CancelledError as error:
Expand Down Expand Up @@ -2622,7 +2642,10 @@ def observe(stage: str) -> None:
)
if target_error_detail is not None:
public_error = f"{public_error} Response: {target_error_detail}"
return ToolResult.error(public_error)
error_result = ToolResult.error(public_error)
if target_terminal_signature is not None:
mark_terminal_failure(error_result, target_terminal_signature)
return error_result
assert response is not None
business_content, body_format = serialize_business_result(response, request, contract)
aliyun_http = build_aliyun_http_metadata(
Expand Down
22 changes: 22 additions & 0 deletions src/iac_code/tools/cloud/aliyun/public_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,18 @@ def public_aliyun_error(
target_http = _target_http_context(code)
if target_http is not None:
status, target_code = target_http
if _is_request_rejected_status(status):
if target_code is not None:
return _(
"Alibaba Cloud API {operation} rejected the request with HTTP {status} and error code {code}. "
"An equivalent request will be rejected again; fix the request or switch to an API path that "
"does not depend on the rejected input."
).format(operation=operation, status=status, code=target_code)
return _(
"Alibaba Cloud API {operation} rejected the request with HTTP {status}. "
"An equivalent request will be rejected again; fix the request or switch to an API path that "
"does not depend on the rejected input."
).format(operation=operation, status=status)
if target_code is not None:
return _(
"Alibaba Cloud API {operation} returned HTTP {status} with error code {code}. "
Expand Down Expand Up @@ -534,6 +546,16 @@ def _target_http_context(code: str) -> tuple[str, str | None] | None:
return status, match.group(2)


def _is_request_rejected_status(status: str) -> bool:
"""Client errors that reject the request shape, so resending is pointless.

408 and 429 are excluded: they depend on timing rather than the request, so
an identical request can legitimately succeed later.
"""
value = int(status)
return 400 <= value < 500 and value not in {408, 429}


def _safe_identifier(value: Any, fallback: str) -> str:
return value if isinstance(value, str) and _SAFE_IDENTIFIER.fullmatch(value) else fallback

Expand Down
Loading
Loading