Metadata-Version: 2.4
Name: dukk
Version: 0.1.0
Summary: SDK oficial Python para a Dukk Platform API
Project-URL: Homepage, https://dukk.com.br
Project-URL: Repository, https://github.com/nextlw/dukk-code
Project-URL: Issues, https://github.com/nextlw/dukk-code/issues
Author: Dukk / Nexcode
License-Expression: MIT
Requires-Python: >=3.10
Requires-Dist: httpx<1.0,>=0.27
Description-Content-Type: text/markdown

# dukk

SDK oficial Python para a **Dukk Platform API** — superfície espelhada do
`openai-python`, sync + async, única dependência de runtime (`httpx`).

## Instalação

```bash
pip install dukk
```

**Requisitos:** Python >= 3.10.

## Quickstart

```python
from dukk import Dukk

client = Dukk()  # ou Dukk(api_key="dukk-...") -- por padrão le DUKK_API_KEY

resp = client.chat.completions.create(
    messages=[
        {"role": "user", "content": 'Crie um script Python que lê um CSV e imprime a média da coluna "valor".'},
    ],
    dukk={
        "deliver": "auto",           # artifacts JSON/text inline; binários por referência
        "conversation_id": "proj-x",  # conversa persistente client-side (opcional)
    },
)

print(resp.choices[0].message.content)

for artifact in (resp.dukk.artifacts if resp.dukk else []):
    if artifact.data is not None:
        print("inline:", artifact.filename, artifact.data)
    else:
        print("referência:", artifact.id, artifact.filename)
```

### Async

```python
import asyncio
from dukk import AsyncDukk

async def main():
    async with AsyncDukk() as client:
        resp = await client.chat.completions.create(
            messages=[{"role": "user", "content": "Olá!"}],
        )
        print(resp.choices[0].message.content)

asyncio.run(main())
```

## Autenticação

A API key pode ser configurada de três formas (em ordem de precedência):

1. **Argumento no construtor:** `Dukk(api_key="dukk-...")`
2. **Variável de ambiente:** `DUKK_API_KEY=dukk-...`
3. Erro claro se ausente em ambos os lugares.

## Opções do cliente

```python
client = Dukk(
    api_key="dukk-...",
    base_url="https://api.dukk.com.br/dukk/compat/v1",  # padrão
    timeout=300.0,      # segundos, padrão 5 min (turnos podem ser longos)
    max_retries=2,      # retry automático em 5xx / erros de rede
    default_headers={"X-My-App": "1.0"},
)
```

## Modelo

O único modelo público é `"dukk"` (ou omitido). O mix de modelos por baixo é
decisão interna da plataforma — passar qualquer outro valor resulta em
`404 model_not_found`.

## Opções Dukk (bloco `dukk`)

O bloco `dukk` no request habilita extensões além do padrão OpenAI:

| Opção             | Tipo                                     | Default   | Descrição                                 |
|-------------------|-------------------------------------------|-----------|--------------------------------------------|
| `sandbox_id`      | `str`                                      | `"default"` | UUID do sandbox ou `"default"`            |
| `deliver`         | `"auto"` \| `"inline"` \| `"reference"`   | `"auto"`  | Política de entrega de artifacts           |
| `conversation_id` | `str`                                      | —         | Chave client-side de conversa persistente |

## Streaming SSE

> **Nota:** streaming requer **plano Enterprise**. Contato: adm@nexcode.live.
> Usuários sem o plano recebem `400 streaming_not_supported_yet`.

```python
for chunk in client.chat.completions.create(
    messages=[{"role": "user", "content": "Escreva um poema."}],
    stream=True,
):
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="")

    if chunk.dukk:
        if "type" in chunk.dukk:
            print("\nevento:", chunk.dukk["type"])
        elif "event" in chunk.dukk:
            print("\nturn_error:", chunk.dukk["error"])
```

## Tabela de erros

Todos os erros são subclasses de `DukkError` e carregam `status`, `code`,
`type` e `param`.

| HTTP | `code`                             | Classe                  |
|------|-------------------------------------|--------------------------|
| 400  | `missing_messages`                  | `BadRequestError`        |
| 400  | `streaming_not_supported_yet`       | `BadRequestError`        |
| 400  | `unsupported_parameter`             | `BadRequestError`        |
| 400  | `invalid_deliver`                   | `BadRequestError`        |
| 400  | `invalid_sandbox_id`                | `BadRequestError`        |
| 400  | `conversation_required_for_tools`   | `BadRequestError`        |
| 401  | `invalid_api_key`                   | `AuthenticationError`    |
| 403  | `insufficient_scope`                | `PermissionDeniedError`  |
| 404  | `model_not_found`                   | `NotFoundError`          |
| 404  | `sandbox_not_found`                 | `NotFoundError`          |
| 404  | `conversation_not_found`            | `NotFoundError`          |
| 404  | `file_not_found`                    | `NotFoundError`          |
| 500  | `db_error`, `session_missing`       | `ServerError`            |
| 502  | `turn_failed`                       | `ServerError`            |
| 504  | `turn_timeout`                      | `ServerError`            |

```python
from dukk import AuthenticationError, NotFoundError

try:
    client.chat.completions.create(messages=[...])
except AuthenticationError as err:
    print("API key inválida:", err.code)  # invalid_api_key
except NotFoundError as err:
    print("Não encontrado:", err.code)  # model_not_found | sandbox_not_found
```

## Conversas persistentes (`client.conversations`)

`dukk.conversation_id` é uma **chave client-side** que identifica uma
conversa persistente (classe isolada `api_client_only` — nunca colide com
conversas desk/web):

```python
# Primeira mensagem
client.chat.completions.create(
    messages=[{"role": "user", "content": "Meu projeto se chama Alfa."}],
    dukk={"conversation_id": "projeto-alfa"},
)

# Segunda mensagem -- o agente lembra do nome
client.chat.completions.create(
    messages=[{"role": "user", "content": "Qual é o nome do meu projeto?"}],
    dukk={"conversation_id": "projeto-alfa"},
)

# Gestão da conversa
conv = client.conversations.retrieve("projeto-alfa")
history = client.conversations.messages("projeto-alfa")
artifacts = client.conversations.artifacts("projeto-alfa")  # paginação fixa: até 100
client.conversations.delete("projeto-alfa")
```

## Licença

MIT — © Dukk / Nexcode
