Skip to content

Commit d0bd967

Browse files
committed
feat: add Qwen-specific DashScope provider
1 parent 2259aa1 commit d0bd967

39 files changed

Lines changed: 5139 additions & 327 deletions

‎babel.cfg‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[python: src/iac_code/**.py]
2-
keywords = translate_message CompletionEnrichmentError CompletionValidationError
2+
keywords = translate_message CompletionEnrichmentError CompletionValidationError ToolCallProtocolError UnsafeStreamProtocolError ProviderRequestLeaseError _localizable_message_id

‎src/iac_code/a2a/events.py‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222

2323
from iac_code.a2a.artifacts import sanitize_public_artifact_text
2424
from iac_code.a2a.exposure import A2AExposureType, normalize_a2a_exposure_types
25-
from iac_code.i18n import _
25+
from iac_code.a2a.runtime_overrides import get_a2a_preferred_language
26+
from iac_code.i18n import translate_message
2627
from iac_code.mcp.progress import mcp_progress_metadata, mcp_progress_public_name
2728
from iac_code.services.permissions.audit import (
2829
build_input_summary,
@@ -122,6 +123,7 @@ async def publish_mcp_warnings(
122123
return
123124
from iac_code.mcp.manager import mcp_warning_metadata
124125

126+
language = get_a2a_preferred_language() or "en"
125127
for warning in warnings[pushed_count:]:
126128
warning_metadata = mcp_warning_metadata(warning)
127129
message = warning_metadata["message"]
@@ -133,7 +135,7 @@ async def publish_mcp_warnings(
133135
message=_agent_text_message(
134136
task_id=task_id,
135137
context_id=context_id,
136-
text=_("MCP warning: {message}").format(message=message),
138+
text=translate_message("MCP warning: {message}", language=language).format(message=message),
137139
),
138140
metadata={
139141
"iac_code": {
@@ -528,11 +530,17 @@ async def publish_stream_event(
528530
error_metadata: dict[str, Any] = {"retryable": event.is_retryable}
529531
if event.error_id:
530532
error_metadata["errorId"] = event.error_id
533+
language = get_a2a_preferred_language() or "en"
531534
if event.is_retryable:
532-
text = _("A temporary error occurred. Please retry.")
535+
text = translate_message("A temporary error occurred. Please retry.", language=language)
533536
state = TaskState.TASK_STATE_INPUT_REQUIRED
534537
else:
535-
raw = event.error or "Unknown error"
538+
if event.i18n_message_id:
539+
raw = translate_message(event.i18n_message_id, language=language).format(
540+
**(event.i18n_message_args or {})
541+
)
542+
else:
543+
raw = event.error or translate_message("Unknown error", language=language)
536544
# Provider ErrorEvent is an explicitly retained compatibility
537545
# exception; ordinary A2A payloads are projected at the wire edge.
538546
text = sanitize_public_artifact_text(raw)[:_ERROR_TEXT_MAX_CHARS]

‎src/iac_code/agent/agent_loop.py‎

Lines changed: 173 additions & 36 deletions
Large diffs are not rendered by default.

‎src/iac_code/cli/output_formats.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,11 @@ def stream_json_event_data(event: StreamEvent) -> dict[str, Any]:
128128
return data
129129

130130
data = dataclasses.asdict(event)
131+
if isinstance(event, MessageEndEvent):
132+
data.pop("usage_attribution", None)
133+
if isinstance(event, ErrorEvent):
134+
data.pop("i18n_message_id", None)
135+
data.pop("i18n_message_args", None)
131136
if isinstance(event, ToolResultEvent):
132137
data.pop("public_path_roots", None)
133138
if data.get("metadata") is None:

‎src/iac_code/i18n/locales/de/LC_MESSAGES/messages.po‎

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ msgstr "afterSequence muss eine nicht negative Ganzzahl sein"
4545
msgid "Unsafe artifact filename"
4646
msgstr "Unsicherer Artefaktdateiname"
4747

48-
#: src/iac_code/a2a/artifacts.py src/iac_code/pipeline/engine/public_errors.py
48+
#: src/iac_code/a2a/artifacts.py src/iac_code/a2a/events.py
49+
#: src/iac_code/pipeline/engine/public_errors.py
4950
#: src/iac_code/utils/public_errors.py
5051
msgid "Unknown error"
5152
msgstr "Unbekannter Fehler"
@@ -6350,6 +6351,37 @@ msgstr ""
63506351
"Kein API-Schlüssel für Anbieter '{provider}' konfiguriert (Modell: "
63516352
"{model}). Führen Sie /auth aus, um die Konfiguration vorzunehmen."
63526353

6354+
#: src/iac_code/providers/manager.py
6355+
#, python-brace-format
6356+
msgid "Provider request lease cannot be consumed from state {state}."
6357+
msgstr ""
6358+
"Die Lease der Provider-Anfrage kann im Zustand {state} nicht verwendet "
6359+
"werden."
6360+
6361+
#: src/iac_code/providers/manager.py
6362+
msgid "Provider request lease was already released."
6363+
msgstr "Die Lease der Provider-Anfrage wurde bereits freigegeben."
6364+
6365+
#: src/iac_code/providers/manager.py
6366+
msgid "Provider request lease belongs to a different manager."
6367+
msgstr "Die Lease der Provider-Anfrage gehört zu einem anderen Manager."
6368+
6369+
#: src/iac_code/providers/manager.py
6370+
msgid "Explicit request lease system prompt does not match the streamed prompt."
6371+
msgstr ""
6372+
"Der System-Prompt der expliziten Anforderungs-Lease stimmt nicht mit dem "
6373+
"Streaming-Prompt überein."
6374+
6375+
#: src/iac_code/providers/manager.py
6376+
msgid "Qwen replay ended before message completion."
6377+
msgstr "Die Qwen-Wiederholung endete, bevor die Nachricht vollständig war."
6378+
6379+
#: src/iac_code/providers/manager.py
6380+
msgid "Explicit request lease system prompt does not match the completion prompt."
6381+
msgstr ""
6382+
"Der System-Prompt der expliziten Anforderungs-Lease stimmt nicht mit dem "
6383+
"Completion-Prompt überein."
6384+
63536385
#: src/iac_code/providers/openai_provider.py
63546386
#, python-brace-format
63556387
msgid ""
@@ -6372,6 +6404,74 @@ msgstr ""
63726404
" Base URL korrekt ist (aktuell: {base_url}). Viele OpenAI-kompatible "
63736405
"Endpunkte erfordern ein /v1-Suffix (z. B. {base_url}/v1)."
63746406

6407+
#: src/iac_code/providers/qwen_tool_call_parser.py
6408+
msgid "Qwen tool arguments must be a non-empty JSON object."
6409+
msgstr "Qwen-Werkzeugargumente müssen ein nicht leeres JSON-Objekt sein."
6410+
6411+
#: src/iac_code/providers/qwen_tool_call_parser.py
6412+
msgid "Qwen tool arguments are malformed JSON."
6413+
msgstr "Qwen-Werkzeugargumente enthalten fehlerhaftes JSON."
6414+
6415+
#: src/iac_code/providers/qwen_tool_call_parser.py
6416+
msgid "Qwen tool arguments must decode to an object."
6417+
msgstr "Qwen-Werkzeugargumente müssen als Objekt dekodiert werden."
6418+
6419+
#: src/iac_code/providers/qwen_tool_call_parser.py
6420+
msgid "Qwen native tool call is missing its ID or name."
6421+
msgstr "Dem nativen Qwen-Werkzeugaufruf fehlt die ID oder der Name."
6422+
6423+
#: src/iac_code/providers/qwen_tool_call_parser.py
6424+
msgid "Invalid Qwen tool-call index."
6425+
msgstr "Ungültiger Index für einen Qwen-Werkzeugaufruf."
6426+
6427+
#: src/iac_code/providers/qwen_tool_call_parser.py
6428+
msgid "Conflicting Qwen tool-call identity."
6429+
msgstr "Widersprüchliche Identität des Qwen-Werkzeugaufrufs."
6430+
6431+
#: src/iac_code/providers/qwen_tool_call_parser.py
6432+
msgid "Conflicting Qwen tool-call name."
6433+
msgstr "Widersprüchlicher Name des Qwen-Werkzeugaufrufs."
6434+
6435+
#: src/iac_code/providers/qwen_tool_call_parser.py
6436+
msgid "Qwen tool-call arguments must be a string."
6437+
msgstr "Die Argumente des Qwen-Werkzeugaufrufs müssen eine Zeichenfolge sein."
6438+
6439+
#: src/iac_code/providers/qwen_tool_call_parser.py
6440+
msgid "Duplicate Qwen tool-call identity."
6441+
msgstr "Doppelte Identität des Qwen-Werkzeugaufrufs."
6442+
6443+
#: src/iac_code/providers/qwen_tool_call_parser.py
6444+
msgid "Ambiguous anonymous Qwen tool-call identity."
6445+
msgstr "Mehrdeutige Identität eines anonymen Qwen-Werkzeugaufrufs."
6446+
6447+
#: src/iac_code/providers/qwen_tool_call_parser.py
6448+
msgid "Ambiguous anonymous Qwen tool-call fragment."
6449+
msgstr "Mehrdeutiges Fragment eines anonymen Qwen-Werkzeugaufrufs."
6450+
6451+
#: src/iac_code/providers/qwen_tool_call_parser.py
6452+
msgid "Invalid Qwen tool-call JSON structure."
6453+
msgstr "Ungültige JSON-Struktur des Qwen-Werkzeugaufrufs."
6454+
6455+
#: src/iac_code/providers/qwen_tool_call_parser.py
6456+
msgid "Qwen response ended with tool_calls but no complete tool call."
6457+
msgstr ""
6458+
"Die Qwen-Antwort endete mit tool_calls, enthält aber keinen vollständigen"
6459+
" Werkzeugaufruf."
6460+
6461+
#: src/iac_code/providers/qwen_tool_call_parser.py
6462+
msgid "Qwen tool call is anonymous or nameless."
6463+
msgstr "Der Qwen-Werkzeugaufruf ist anonym oder hat keinen Namen."
6464+
6465+
#: src/iac_code/providers/qwen_tool_call_parser.py
6466+
msgid "Duplicate Qwen tool-call ID."
6467+
msgstr "Doppelte ID des Qwen-Werkzeugaufrufs."
6468+
6469+
#: src/iac_code/providers/qwen_tool_call_parser.py
6470+
msgid "Qwen tool-call arguments ended before JSON was complete."
6471+
msgstr ""
6472+
"Die Argumente des Qwen-Werkzeugaufrufs endeten, bevor das JSON "
6473+
"vollständig war."
6474+
63756475
#: src/iac_code/providers/registry.py
63766476
msgid "Alibaba Cloud Bailian"
63776477
msgstr "Alibaba Cloud Bailian"
@@ -6448,6 +6548,28 @@ msgstr "Volcengine CodingPlan"
64486548
msgid "Anthropic Compatible"
64496549
msgstr "Anthropic-kompatibel"
64506550

6551+
#: src/iac_code/providers/streaming.py
6552+
msgid "Qwen thinking-tag candidate exceeded the safety limit."
6553+
msgstr ""
6554+
"Der Qwen-Kandidat für ein thinking-Tag hat das Sicherheitslimit "
6555+
"überschritten."
6556+
6557+
#: src/iac_code/providers/streaming.py
6558+
msgid "Qwen emitted an unsafe or conflicting thinking-tag block."
6559+
msgstr ""
6560+
"Qwen hat einen unsicheren oder widersprüchlichen thinking-Tag-Block "
6561+
"ausgegeben."
6562+
6563+
#: src/iac_code/providers/streaming.py
6564+
msgid "Qwen emitted a stray thinking closing tag."
6565+
msgstr "Qwen hat ein einzelnes schließendes thinking-Tag ausgegeben."
6566+
6567+
#: src/iac_code/providers/streaming.py
6568+
msgid "Qwen emitted a closing thinking tag without a valid native tool call."
6569+
msgstr ""
6570+
"Qwen hat ein schließendes thinking-Tag ohne gültigen nativen "
6571+
"Werkzeugaufruf ausgegeben."
6572+
64516573
#: src/iac_code/services/agent_factory.py
64526574
#, python-brace-format
64536575
msgid "MCP server {server!r} connection failed: {error}"

‎src/iac_code/i18n/locales/es/LC_MESSAGES/messages.po‎

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ msgstr "afterSequence debe ser un entero no negativo"
4646
msgid "Unsafe artifact filename"
4747
msgstr "Nombre de archivo de artefacto no seguro"
4848

49-
#: src/iac_code/a2a/artifacts.py src/iac_code/pipeline/engine/public_errors.py
49+
#: src/iac_code/a2a/artifacts.py src/iac_code/a2a/events.py
50+
#: src/iac_code/pipeline/engine/public_errors.py
5051
#: src/iac_code/utils/public_errors.py
5152
msgid "Unknown error"
5253
msgstr "Error desconocido"
@@ -6300,6 +6301,37 @@ msgstr ""
63006301
"No se ha configurado una clave API para el proveedor '{provider}' "
63016302
"(modelo: {model}). Ejecute /auth para configurar."
63026303

6304+
#: src/iac_code/providers/manager.py
6305+
#, python-brace-format
6306+
msgid "Provider request lease cannot be consumed from state {state}."
6307+
msgstr ""
6308+
"La reserva de la solicitud al proveedor no se puede consumir desde el "
6309+
"estado {state}."
6310+
6311+
#: src/iac_code/providers/manager.py
6312+
msgid "Provider request lease was already released."
6313+
msgstr "La reserva de la solicitud al proveedor ya se liberó."
6314+
6315+
#: src/iac_code/providers/manager.py
6316+
msgid "Provider request lease belongs to a different manager."
6317+
msgstr "La reserva de la solicitud al proveedor pertenece a otro gestor."
6318+
6319+
#: src/iac_code/providers/manager.py
6320+
msgid "Explicit request lease system prompt does not match the streamed prompt."
6321+
msgstr ""
6322+
"El prompt del sistema de la reserva de solicitud explícita no coincide "
6323+
"con el prompt de streaming."
6324+
6325+
#: src/iac_code/providers/manager.py
6326+
msgid "Qwen replay ended before message completion."
6327+
msgstr "La repetición de Qwen terminó antes de completarse el mensaje."
6328+
6329+
#: src/iac_code/providers/manager.py
6330+
msgid "Explicit request lease system prompt does not match the completion prompt."
6331+
msgstr ""
6332+
"El prompt del sistema de la reserva de solicitud explícita no coincide "
6333+
"con el prompt de finalización."
6334+
63036335
#: src/iac_code/providers/openai_provider.py
63046336
#, python-brace-format
63056337
msgid ""
@@ -6322,6 +6354,74 @@ msgstr ""
63226354
" correcta (actual: {base_url}). Muchos endpoints compatibles con OpenAI "
63236355
"requieren el sufijo /v1 (p. ej., {base_url}/v1)."
63246356

6357+
#: src/iac_code/providers/qwen_tool_call_parser.py
6358+
msgid "Qwen tool arguments must be a non-empty JSON object."
6359+
msgstr "Los argumentos de herramienta de Qwen deben ser un objeto JSON no vacío."
6360+
6361+
#: src/iac_code/providers/qwen_tool_call_parser.py
6362+
msgid "Qwen tool arguments are malformed JSON."
6363+
msgstr "Los argumentos de herramienta de Qwen contienen JSON no válido."
6364+
6365+
#: src/iac_code/providers/qwen_tool_call_parser.py
6366+
msgid "Qwen tool arguments must decode to an object."
6367+
msgstr "Los argumentos de herramienta de Qwen deben decodificarse como un objeto."
6368+
6369+
#: src/iac_code/providers/qwen_tool_call_parser.py
6370+
msgid "Qwen native tool call is missing its ID or name."
6371+
msgstr "A la llamada nativa a herramienta de Qwen le falta el ID o el nombre."
6372+
6373+
#: src/iac_code/providers/qwen_tool_call_parser.py
6374+
msgid "Invalid Qwen tool-call index."
6375+
msgstr "El índice de la llamada a herramienta de Qwen no es válido."
6376+
6377+
#: src/iac_code/providers/qwen_tool_call_parser.py
6378+
msgid "Conflicting Qwen tool-call identity."
6379+
msgstr "La identidad de la llamada a herramienta de Qwen está en conflicto."
6380+
6381+
#: src/iac_code/providers/qwen_tool_call_parser.py
6382+
msgid "Conflicting Qwen tool-call name."
6383+
msgstr "El nombre de la llamada a herramienta de Qwen está en conflicto."
6384+
6385+
#: src/iac_code/providers/qwen_tool_call_parser.py
6386+
msgid "Qwen tool-call arguments must be a string."
6387+
msgstr "Los argumentos de la llamada a herramienta de Qwen deben ser una cadena."
6388+
6389+
#: src/iac_code/providers/qwen_tool_call_parser.py
6390+
msgid "Duplicate Qwen tool-call identity."
6391+
msgstr "La identidad de la llamada a herramienta de Qwen está duplicada."
6392+
6393+
#: src/iac_code/providers/qwen_tool_call_parser.py
6394+
msgid "Ambiguous anonymous Qwen tool-call identity."
6395+
msgstr "La identidad de una llamada anónima a herramienta de Qwen es ambigua."
6396+
6397+
#: src/iac_code/providers/qwen_tool_call_parser.py
6398+
msgid "Ambiguous anonymous Qwen tool-call fragment."
6399+
msgstr "El fragmento de una llamada anónima a herramienta de Qwen es ambiguo."
6400+
6401+
#: src/iac_code/providers/qwen_tool_call_parser.py
6402+
msgid "Invalid Qwen tool-call JSON structure."
6403+
msgstr "La estructura JSON de la llamada a herramienta de Qwen no es válida."
6404+
6405+
#: src/iac_code/providers/qwen_tool_call_parser.py
6406+
msgid "Qwen response ended with tool_calls but no complete tool call."
6407+
msgstr ""
6408+
"La respuesta de Qwen terminó con tool_calls, pero no contiene ninguna "
6409+
"llamada a herramienta completa."
6410+
6411+
#: src/iac_code/providers/qwen_tool_call_parser.py
6412+
msgid "Qwen tool call is anonymous or nameless."
6413+
msgstr "La llamada a herramienta de Qwen es anónima o no tiene nombre."
6414+
6415+
#: src/iac_code/providers/qwen_tool_call_parser.py
6416+
msgid "Duplicate Qwen tool-call ID."
6417+
msgstr "El ID de la llamada a herramienta de Qwen está duplicado."
6418+
6419+
#: src/iac_code/providers/qwen_tool_call_parser.py
6420+
msgid "Qwen tool-call arguments ended before JSON was complete."
6421+
msgstr ""
6422+
"Los argumentos de la llamada a herramienta de Qwen terminaron antes de "
6423+
"que se completara el JSON."
6424+
63256425
#: src/iac_code/providers/registry.py
63266426
msgid "Alibaba Cloud Bailian"
63276427
msgstr "Alibaba Cloud Bailian"
@@ -6398,6 +6498,24 @@ msgstr "Volcengine CodingPlan"
63986498
msgid "Anthropic Compatible"
63996499
msgstr "Compatible con Anthropic"
64006500

6501+
#: src/iac_code/providers/streaming.py
6502+
msgid "Qwen thinking-tag candidate exceeded the safety limit."
6503+
msgstr "El candidato a etiqueta thinking de Qwen superó el límite de seguridad."
6504+
6505+
#: src/iac_code/providers/streaming.py
6506+
msgid "Qwen emitted an unsafe or conflicting thinking-tag block."
6507+
msgstr "Qwen generó un bloque de etiquetas thinking no seguro o en conflicto."
6508+
6509+
#: src/iac_code/providers/streaming.py
6510+
msgid "Qwen emitted a stray thinking closing tag."
6511+
msgstr "Qwen generó una etiqueta de cierre thinking aislada."
6512+
6513+
#: src/iac_code/providers/streaming.py
6514+
msgid "Qwen emitted a closing thinking tag without a valid native tool call."
6515+
msgstr ""
6516+
"Qwen generó una etiqueta de cierre thinking sin una llamada nativa a "
6517+
"herramienta válida."
6518+
64016519
#: src/iac_code/services/agent_factory.py
64026520
#, python-brace-format
64036521
msgid "MCP server {server!r} connection failed: {error}"

0 commit comments

Comments
 (0)