Metadata-Version: 2.4
Name: p2d-duck
Version: 1.3.0
Summary: Free Python client for DuckDuckGo AI Chat (duck.ai). Sync, streaming, image generation, image edit, multimodal vision, web search. Auto-retry on challenge failures. No API key required.
Project-URL: Homepage, https://github.com/pooraddyy/p2d-duck
Project-URL: Repository, https://github.com/pooraddyy/p2d-duck
Project-URL: Issues, https://github.com/pooraddyy/p2d-duck/issues
Author: duck-ai contributors
License: MIT
License-File: LICENSE
Keywords: ai,chatbot,chatgpt,claude,duck.ai,duckchat,duckduckgo,image-generation,llm,mistral,multimodal
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: cryptography>=41
Requires-Dist: html5lib>=1.1
Requires-Dist: httpx>=0.27
Requires-Dist: mini-racer>=0.12
Provides-Extra: dev
Requires-Dist: build; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: twine; extra == 'dev'
Description-Content-Type: text/markdown

# p2d-duck

```
pip install p2d-duck
```

Free, no-API-key Python client for **DuckDuckGo AI Chat** ([duck.ai](https://duck.ai)).
Import name: `duck_ai`.

---

```
Features
--------
  Single sync client built on httpx
  Auto-retry with exponential backoff on challenge failures
  Browser-faithful session warm-up — first call succeeds, not the third
  6 chat models + image generation (see model table below)
  Reasoning effort control  (fast / reasoning)
  Image generation  (text-to-image)
  Image edit        (caption + source image -> edited image)
  Image upload      (vision / multimodal)
  Web search        (opt-in per call, supported models only)
  Multi-turn history (off by default, opt-in)
  Built-in x-vqd-hash-1 JS challenge solver via mini-racer
  CLI: p2d-duck
  No account · No API key · No server · No fee
```

---

## Install

```bash
pip install p2d-duck
```

Requires Python 3.10+.

---

## Models

```
Alias            Model ID                                   Reasoning   Vision   Web Search
-----------      -----------------------------------------  ---------   ------   ----------
gpt5_mini        gpt-5.4-mini                               yes         yes      yes
gpt5_nano        gpt-5.4-nano                               yes         yes      yes
claude           claude-haiku-4-5                           yes         yes      yes
mistral          mistral-small-2603                         no          no       no
gpt_oss          tinfoil/gpt-oss-120b                       yes         no       no
image            image-generation                           no          yes      no
```

Pass any alias, full model ID, or `ModelType` enum member to `DuckChat(model=...)`.

```python
from duck_ai import (
    DuckChat,
    gpt5_mini, gpt5_nano,
    claude, mistral, gpt_oss, image_generation,
)
```

---

## Quickstart

```python
from duck_ai import DuckChat, gpt5_mini

with DuckChat(model=gpt5_mini) as duck:
    print(duck.ask("Explain quantum tunneling in one sentence."))
```

---

## Streaming

```python
from duck_ai import DuckChat

with DuckChat() as duck:
    for chunk in duck.stream("Write a 4-line haiku about ducks."):
        print(chunk, end="", flush=True)
```

---

## Reasoning effort

```python
from duck_ai import DuckChat, gpt5_mini, claude

with DuckChat(model=gpt5_mini, effort="fast") as duck:
    print(duck.ask("Quick: 2+2?"))

with DuckChat(model=claude, effort="reasoning") as duck:
    print(duck.ask("Solve: I speak without a mouth..."))
```

```
Model         fast token    reasoning token    Default
-----------   ----------    ---------------    -------
gpt5_mini     minimal       low                low
gpt5_nano     minimal       low                low
claude        none          low                low
gpt_oss       low           low                low
mistral       (none)        (none)             (none)
```

Pass `effort="fast"` or `effort="reasoning"`. Non-reasoning models silently ignore it.

---

## Multi-turn conversation

History is **off by default**. Each `ask` / `stream` call is a fresh single-turn request
unless you opt in.

```python
from duck_ai import DuckChat, claude

# Per-client: enable at construction
with DuckChat(model=claude, history=True) as duck:
    duck.ask("My name is Alice. Remember it.")
    print(duck.ask("What is my name?"))   # -> Alice
    duck.reset()                          # clear the buffered turns

# Toggle live
with DuckChat(model=claude) as duck:
    duck.enable_history()
    duck.ask("Pick a number between 1 and 10.")
    print(duck.ask("What number did you pick?"))
    duck.disable_history()                # also clears the buffer

# Per-call override
with DuckChat() as duck:
    duck.ask("hi", remember=True)         # buffer this turn
    duck.ask("ignore me", remember=False) # do not buffer
```

---

## Web search

```python
from duck_ai import DuckChat, gpt5_mini, model_supports_web_search

assert model_supports_web_search(gpt5_mini)

with DuckChat(model=gpt5_mini) as duck:
    print(duck.ask(
        "What did Apple announce at WWDC this year?",
        web_search=True,
    ))
```

Supported on: `gpt5_mini`, `gpt5_nano`, `claude`. Silently ignored for other models.

---

## Image generation

```python
from duck_ai import DuckChat, image_generation

with DuckChat(model=image_generation) as duck:
    duck.generate_image(
        "a cute rubber duck wearing a wizard hat, digital art",
        save_to="duck_wizard.jpg",
    )
```

---

## Image edit

```python
from duck_ai import DuckChat, image_generation

with DuckChat(model=image_generation) as duck:
    duck.edit_image(
        "make the duck wear a tiny chef hat",
        "duck_wizard.jpg",
        save_to="duck_chef.jpg",
    )
```

---

## Image upload (multimodal vision)

```python
from duck_ai import DuckChat, ImagePart

with DuckChat() as duck:
    print(duck.ask_with_image("What is in this image?", "photo.jpg"))

    print(duck.ask([
        "Compare these two images:",
        ImagePart.from_path("a.png"),
        ImagePart.from_path("b.png"),
    ]))
```

If the selected model has no vision capability, the request is automatically
routed to a vision-capable model (`gpt-5.4-mini`).

---

## CLI

```
p2d-duck                                         interactive REPL
p2d-duck chat "Hello, who are you?"
p2d-duck -m claude chat "Hi Claude!"
p2d-duck -m gpt5_mini -e reasoning chat "Solve x^2 - 5x + 6 = 0"
p2d-duck chat "Describe this" --image cat.jpg
p2d-duck -m gpt5_mini chat "Top news today" --web-search
p2d-duck image "a watercolor moon over a lake" -o moon.jpg
p2d-duck edit "make the cat wear sunglasses" --image cat.jpg -o cat_cool.jpg
p2d-duck models
```

REPL commands: `/reset`, `/history on|off`, `/quit`

---

## Reliability

```
1. Session warm-up     — visits duck.ai homepage on construction so cookies are
                         present before the first chat request.
2. Challenge rotation  — captures x-vqd-hash-1 from each response and reuses it
                         for the next call, matching the browser client behaviour.
3. Retry loop          — on ChallengeError, RemoteProtocolError, RateLimitError,
                         dropped streams, or an empty SSE response: re-fetches the
                         challenge and retries with exponential backoff + jitter.
4. Terminal errors     — ConversationLimitError is never retried.
5. Real RSA key        — uses cryptography to generate a real RSA-OAEP-256 JWK for
                         durable streams; refuses to fall back to a fake key.
```

```python
duck = DuckChat(max_retries=4, backoff_base=0.6)
```

---

## Exceptions

```
Exception               When
--------------------    -----------------------------------------------
DuckChatError           Generic error — base class for all below.
ChallengeError          Could not solve the x-vqd-hash-1 JS challenge.
RateLimitError          HTTP 429 from the server.
ConversationLimitError  Too many turns in one session (terminal).
APIError                Any other non-200 response (.status_code, .body).
```

---

## How it works

DuckDuckGo's AI Chat backend (`duck.ai/duckchat/v1/*`) requires a per-request
proof-of-work challenge encoded in the `x-vqd-hash-1` header. The server returns
an obfuscated JavaScript snippet that must be evaluated in a browser-like environment.

```
1. stubs.js     — minimal browser-DOM shim injected before the challenge script.
2. mini-racer   — embedded V8 isolate (no external Node.js install required).
3. SHA-256      — hashes the fingerprint values produced by the challenge.
4. RSA-OAEP-256 — real public key for durable (resumable) streams.
```

---

## License

MIT. See [LICENSE](LICENSE).

---

## Disclaimer

Unofficial reverse-engineered client. Not affiliated with or endorsed by DuckDuckGo.
Use at your own risk and respect [duck.ai](https://duck.ai)'s terms of service.
The backend may change at any time.
