Download src/core/harness/capture/providers.py from openenv/echo_env: direct link, hf CLI and curl.
- Browser
- Download file 12.1 kB
-
https://huggingface.co/spaces/openenv/echo_env/resolve/main/src/core/harness/capture/providers.py
- Command line
-
hf download hf://spaces/openenv/echo_env/src/core/harness/capture/providers.py
-
curl -L -o providers.py https://huggingface.co/spaces/openenv/echo_env/resolve/main/src/core/harness/capture/providers.py
12.1 kB
| """Native upstream conversion; independent of the harness-facing wire protocol.""" | |
| from __future__ import annotations | |
| import copy | |
| import json | |
| import time | |
| from typing import Any | |
| from .dialects.images import openai_chat_content_to_anthropic_blocks | |
| from .upstream import UpstreamRequestError | |
| class ProviderConversionError(UpstreamRequestError): | |
| """A request cannot be translated without losing semantics; retrying cannot fix it.""" | |
| def _content_blocks(content): | |
| if content is None: | |
| return [] | |
| if not isinstance(content, (str, list)): | |
| raise ProviderConversionError( | |
| "Anthropic conversion requires string or block-list content" | |
| ) | |
| if isinstance(content, list): | |
| for part in content: | |
| if isinstance(part, str): | |
| continue | |
| if not isinstance(part, dict) or part.get("type") not in { | |
| "text", | |
| "image_url", | |
| }: | |
| raise ProviderConversionError( | |
| "Anthropic conversion cannot preserve this content block" | |
| ) | |
| if part["type"] == "text" and not isinstance(part.get("text"), str): | |
| raise ProviderConversionError("text content must be a string") | |
| if part["type"] == "image_url": | |
| from .dialects.images import openai_chat_image_to_anthropic | |
| if openai_chat_image_to_anthropic(part) is None: | |
| raise ProviderConversionError("image content cannot be converted") | |
| return openai_chat_content_to_anthropic_blocks(content) | |
| def anthropic_request(request: dict[str, Any], model: str) -> dict[str, Any]: | |
| """Convert canonical chat input, or preserve an original native Messages request.""" | |
| original = request.get("_openenv_native_request") | |
| if original is not None: | |
| body = copy.deepcopy(original) | |
| body.pop("_served_model", None) | |
| body["model"] = model | |
| body["stream"] = False | |
| if "temperature" in body and body.get("top_p") == 1: | |
| body.pop("top_p") | |
| if body.get("top_k") == -1: | |
| body.pop("top_k") | |
| # The proxy's cap remains authoritative for native requests too. | |
| body["max_tokens"] = min( | |
| body.get("max_tokens", 4096), request.get("max_tokens", 4096) | |
| ) | |
| return body | |
| unsupported = [ | |
| k | |
| for k in ("response_format", "logit_bias", "audio", "modalities") | |
| if request.get(k) | |
| ] | |
| for key, neutral in ( | |
| ("frequency_penalty", 0), | |
| ("presence_penalty", 0), | |
| ("repetition_penalty", 1), | |
| ("min_p", 0), | |
| ): | |
| if request.get(key) is not None and request[key] != neutral: | |
| unsupported.append(key) | |
| if unsupported: | |
| raise ProviderConversionError( | |
| "native Anthropic conversion cannot preserve: " + ", ".join(unsupported) | |
| ) | |
| body: dict[str, Any] = { | |
| "model": model, | |
| "max_tokens": request.get( | |
| "max_tokens", request.get("max_completion_tokens", 4096) | |
| ), | |
| "stream": False, | |
| } | |
| for key in ("temperature", "top_p", "top_k"): | |
| if key in request and request[key] is not None: | |
| if (key == "top_k" and request[key] == -1) or ( | |
| key == "top_p" and request[key] == 1 | |
| ): | |
| continue | |
| body[key] = request[key] | |
| if "stop" in request: | |
| stop = request["stop"] | |
| body["stop_sequences"] = [stop] if isinstance(stop, str) else stop | |
| messages = [] | |
| system = [] | |
| for message in request.get("messages", []): | |
| role = message["role"] | |
| content = message.get("content") | |
| if role in ("system", "developer"): | |
| if messages: | |
| raise ProviderConversionError( | |
| "Anthropic cannot preserve a system instruction inserted after conversation turns" | |
| ) | |
| system.extend(_content_blocks(content)) | |
| continue | |
| if role == "tool": | |
| native = { | |
| "role": "user", | |
| "content": [ | |
| { | |
| "type": "tool_result", | |
| "tool_use_id": message["tool_call_id"], | |
| "content": _content_blocks(content), | |
| } | |
| ], | |
| } | |
| elif role in ("user", "assistant"): | |
| blocks = _content_blocks(content) if content else [] | |
| if message.get("reasoning_content") or message.get("reasoning"): | |
| raise ProviderConversionError( | |
| "Anthropic thinking history requires original signed native blocks" | |
| ) | |
| for call in message.get("tool_calls") or []: | |
| function = call["function"] | |
| args = function.get("arguments", {}) | |
| if isinstance(args, str): | |
| try: | |
| args = json.loads(args) | |
| except ValueError as exc: | |
| raise ProviderConversionError( | |
| "tool arguments must be a JSON object" | |
| ) from exc | |
| if not isinstance(args, dict): | |
| raise ProviderConversionError( | |
| "tool arguments must be a JSON object" | |
| ) | |
| blocks.append( | |
| { | |
| "type": "tool_use", | |
| "id": call["id"], | |
| "name": function["name"], | |
| "input": args, | |
| } | |
| ) | |
| native = {"role": role, "content": blocks} | |
| else: | |
| raise ProviderConversionError(f"unsupported role for Anthropic: {role}") | |
| if messages and messages[-1]["role"] == native["role"]: | |
| messages[-1]["content"].extend(native["content"]) | |
| else: | |
| messages.append(native) | |
| body["messages"] = messages | |
| if system: | |
| body["system"] = system | |
| if request.get("tools"): | |
| tools = [] | |
| for tool in request["tools"]: | |
| if tool.get("type") != "function": | |
| raise ProviderConversionError( | |
| "native Anthropic conversion requires function tools" | |
| ) | |
| function = tool["function"] | |
| tools.append( | |
| { | |
| "name": function["name"], | |
| **({"strict": function["strict"]} if "strict" in function else {}), | |
| "description": function.get("description", ""), | |
| "input_schema": function.get( | |
| "parameters", {"type": "object", "properties": {}} | |
| ), | |
| } | |
| ) | |
| body["tools"] = tools | |
| choice = request.get("tool_choice") | |
| if choice in ("auto", "none", "required"): | |
| body["tool_choice"] = {"type": "any" if choice == "required" else choice} | |
| elif isinstance(choice, dict): | |
| body["tool_choice"] = {"type": "tool", "name": choice["function"]["name"]} | |
| if request.get("parallel_tool_calls") is False and body.get("tools"): | |
| body.setdefault("tool_choice", {"type": "auto"})[ | |
| "disable_parallel_tool_use" | |
| ] = True | |
| return body | |
| def anthropic_response( | |
| native: dict[str, Any], *, native_passthrough: bool = False | |
| ) -> dict[str, Any]: | |
| """Keep the original response while normalizing text/tool calls for capture.""" | |
| text, reasoning, calls = [], [], [] | |
| if not native_passthrough and native.get("stop_reason") not in { | |
| None, | |
| "end_turn", | |
| "stop_sequence", | |
| "tool_use", | |
| "max_tokens", | |
| }: | |
| raise ProviderConversionError( | |
| "Anthropic stop reason cannot be represented by this harness protocol" | |
| ) | |
| for block in native.get("content", []): | |
| if not native_passthrough and ( | |
| block.get("type") not in {"text", "thinking", "tool_use"} | |
| or block.get("citations") | |
| ): | |
| raise ProviderConversionError( | |
| "Anthropic response block cannot be preserved by this harness protocol: " | |
| + str(block.get("type")) | |
| ) | |
| if block["type"] == "text": | |
| text.append(block["text"]) | |
| elif block["type"] == "thinking": | |
| reasoning.append(block["thinking"]) | |
| elif block["type"] == "tool_use": | |
| calls.append( | |
| { | |
| "id": block["id"], | |
| "type": "function", | |
| "function": { | |
| "name": block["name"], | |
| "arguments": json.dumps(block["input"], ensure_ascii=False), | |
| }, | |
| } | |
| ) | |
| usage = native.get("usage", {}) | |
| prompt = sum( | |
| usage.get(k, 0) | |
| for k in ( | |
| "input_tokens", | |
| "cache_creation_input_tokens", | |
| "cache_read_input_tokens", | |
| ) | |
| ) | |
| output = usage.get("output_tokens", 0) | |
| return { | |
| "id": native["id"], | |
| "object": "chat.completion", | |
| "created": int(time.time()), | |
| "model": native["model"], | |
| "choices": [ | |
| { | |
| "index": 0, | |
| "message": { | |
| "role": "assistant", | |
| "content": "".join(text), | |
| "reasoning_content": "".join(reasoning) or None, | |
| "tool_calls": calls or None, | |
| }, | |
| "finish_reason": {"tool_use": "tool_calls", "max_tokens": "length"}.get( | |
| native.get("stop_reason"), "stop" | |
| ), | |
| "logprobs": None, | |
| } | |
| ], | |
| "usage": { | |
| "prompt_tokens": prompt, | |
| "completion_tokens": output, | |
| "total_tokens": prompt + output, | |
| }, | |
| "_openenv_native_response": copy.deepcopy(native), | |
| } | |
| def replay_anthropic(native: dict[str, Any]): | |
| """Emit SDK-compatible events without replacing native thinking signatures.""" | |
| def frame(event): | |
| return ( | |
| "event: " | |
| + event["type"] | |
| + "\ndata: " | |
| + json.dumps(event, ensure_ascii=False) | |
| + "\n\n" | |
| ) | |
| message = { | |
| **native, | |
| "content": [], | |
| "stop_reason": None, | |
| "stop_sequence": None, | |
| "usage": {**native.get("usage", {}), "output_tokens": 0}, | |
| } | |
| yield frame({"type": "message_start", "message": message}) | |
| for index, block in enumerate(native.get("content", [])): | |
| initial = dict(block) | |
| deltas = [] | |
| if block["type"] == "text": | |
| initial["text"] = "" | |
| deltas.append({"type": "text_delta", "text": block["text"]}) | |
| elif block["type"] == "tool_use": | |
| initial["input"] = {} | |
| deltas.append( | |
| { | |
| "type": "input_json_delta", | |
| "partial_json": json.dumps(block["input"], ensure_ascii=False), | |
| } | |
| ) | |
| elif block["type"] == "thinking": | |
| initial.update(thinking="", signature="") | |
| deltas.extend( | |
| [ | |
| {"type": "thinking_delta", "thinking": block["thinking"]}, | |
| {"type": "signature_delta", "signature": block["signature"]}, | |
| ] | |
| ) | |
| yield frame( | |
| {"type": "content_block_start", "index": index, "content_block": initial} | |
| ) | |
| for delta in deltas: | |
| yield frame({"type": "content_block_delta", "index": index, "delta": delta}) | |
| yield frame({"type": "content_block_stop", "index": index}) | |
| yield frame( | |
| { | |
| "type": "message_delta", | |
| "delta": { | |
| "stop_reason": native.get("stop_reason"), | |
| "stop_sequence": native.get("stop_sequence"), | |
| }, | |
| "usage": {"output_tokens": native.get("usage", {}).get("output_tokens", 0)}, | |
| } | |
| ) | |
| yield frame({"type": "message_stop"}) | |