iicp-proxy 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. iicp_proxy-0.2.0/PKG-INFO +23 -0
  2. iicp_proxy-0.2.0/README.md +224 -0
  3. iicp_proxy-0.2.0/pyproject.toml +59 -0
  4. iicp_proxy-0.2.0/setup.cfg +4 -0
  5. iicp_proxy-0.2.0/src/iicp_proxy.egg-info/PKG-INFO +23 -0
  6. iicp_proxy-0.2.0/src/iicp_proxy.egg-info/SOURCES.txt +85 -0
  7. iicp_proxy-0.2.0/src/iicp_proxy.egg-info/dependency_links.txt +1 -0
  8. iicp_proxy-0.2.0/src/iicp_proxy.egg-info/entry_points.txt +2 -0
  9. iicp_proxy-0.2.0/src/iicp_proxy.egg-info/requires.txt +19 -0
  10. iicp_proxy-0.2.0/src/iicp_proxy.egg-info/top_level.txt +1 -0
  11. iicp_proxy-0.2.0/src/proxy/__init__.py +2 -0
  12. iicp_proxy-0.2.0/src/proxy/address_state.py +29 -0
  13. iicp_proxy-0.2.0/src/proxy/anthropic_compat/__init__.py +1 -0
  14. iicp_proxy-0.2.0/src/proxy/anthropic_compat/server.py +207 -0
  15. iicp_proxy-0.2.0/src/proxy/anthropic_compat/translator.py +101 -0
  16. iicp_proxy-0.2.0/src/proxy/auth/__init__.py +4 -0
  17. iicp_proxy-0.2.0/src/proxy/auth/secrets.py +18 -0
  18. iicp_proxy-0.2.0/src/proxy/cip/__init__.py +1 -0
  19. iicp_proxy-0.2.0/src/proxy/cip/aggregation.py +77 -0
  20. iicp_proxy-0.2.0/src/proxy/cip/consumer.py +109 -0
  21. iicp_proxy-0.2.0/src/proxy/cip/coordinator.py +108 -0
  22. iicp_proxy-0.2.0/src/proxy/cip/dispatch.py +141 -0
  23. iicp_proxy-0.2.0/src/proxy/cip/gates.py +250 -0
  24. iicp_proxy-0.2.0/src/proxy/cip/receipts.py +154 -0
  25. iicp_proxy-0.2.0/src/proxy/cip/strategies.py +100 -0
  26. iicp_proxy-0.2.0/src/proxy/clients/__init__.py +5 -0
  27. iicp_proxy-0.2.0/src/proxy/clients/did_resolver.py +99 -0
  28. iicp_proxy-0.2.0/src/proxy/clients/directory.py +360 -0
  29. iicp_proxy-0.2.0/src/proxy/clients/node.py +130 -0
  30. iicp_proxy-0.2.0/src/proxy/clients/replica_registry.py +143 -0
  31. iicp_proxy-0.2.0/src/proxy/clients/replica_sig_verifier.py +98 -0
  32. iicp_proxy-0.2.0/src/proxy/clients/trust_resolver.py +127 -0
  33. iicp_proxy-0.2.0/src/proxy/config.py +139 -0
  34. iicp_proxy-0.2.0/src/proxy/main.py +188 -0
  35. iicp_proxy-0.2.0/src/proxy/metrics.py +30 -0
  36. iicp_proxy-0.2.0/src/proxy/network/__init__.py +4 -0
  37. iicp_proxy-0.2.0/src/proxy/network/peer_cache.py +118 -0
  38. iicp_proxy-0.2.0/src/proxy/ollama_compat/__init__.py +1 -0
  39. iicp_proxy-0.2.0/src/proxy/ollama_compat/server.py +187 -0
  40. iicp_proxy-0.2.0/src/proxy/ollama_compat/translator.py +96 -0
  41. iicp_proxy-0.2.0/src/proxy/openai_compat/__init__.py +5 -0
  42. iicp_proxy-0.2.0/src/proxy/openai_compat/server.py +130 -0
  43. iicp_proxy-0.2.0/src/proxy/openai_compat/translator.py +38 -0
  44. iicp_proxy-0.2.0/src/proxy/otel_tracer.py +190 -0
  45. iicp_proxy-0.2.0/src/proxy/plugin/__init__.py +2 -0
  46. iicp_proxy-0.2.0/src/proxy/plugin/iicp_provider.py +76 -0
  47. iicp_proxy-0.2.0/src/proxy/routing/__init__.py +15 -0
  48. iicp_proxy-0.2.0/src/proxy/routing/aggregator.py +115 -0
  49. iicp_proxy-0.2.0/src/proxy/routing/circuit_breaker.py +92 -0
  50. iicp_proxy-0.2.0/src/proxy/routing/fallback.py +259 -0
  51. iicp_proxy-0.2.0/src/proxy/routing/retry.py +96 -0
  52. iicp_proxy-0.2.0/src/proxy/routing/router.py +100 -0
  53. iicp_proxy-0.2.0/src/proxy/routing/selector.py +180 -0
  54. iicp_proxy-0.2.0/tests/test_address_learning.py +199 -0
  55. iicp_proxy-0.2.0/tests/test_aggregator.py +79 -0
  56. iicp_proxy-0.2.0/tests/test_anthropic_cip_dispatch.py +104 -0
  57. iicp_proxy-0.2.0/tests/test_anthropic_compat.py +137 -0
  58. iicp_proxy-0.2.0/tests/test_cip_aggregation.py +302 -0
  59. iicp_proxy-0.2.0/tests/test_cip_call_validators.py +261 -0
  60. iicp_proxy-0.2.0/tests/test_cip_consumer.py +73 -0
  61. iicp_proxy-0.2.0/tests/test_cip_coordinator.py +1120 -0
  62. iicp_proxy-0.2.0/tests/test_cip_integration.py +197 -0
  63. iicp_proxy-0.2.0/tests/test_circuit_breaker.py +35 -0
  64. iicp_proxy-0.2.0/tests/test_compat_routes.py +269 -0
  65. iicp_proxy-0.2.0/tests/test_coverage_gates.py +114 -0
  66. iicp_proxy-0.2.0/tests/test_did_resolver.py +103 -0
  67. iicp_proxy-0.2.0/tests/test_directory_client.py +446 -0
  68. iicp_proxy-0.2.0/tests/test_directory_redirect_trust.py +183 -0
  69. iicp_proxy-0.2.0/tests/test_directory_sig_verify.py +236 -0
  70. iicp_proxy-0.2.0/tests/test_fallback.py +556 -0
  71. iicp_proxy-0.2.0/tests/test_node_client.py +166 -0
  72. iicp_proxy-0.2.0/tests/test_node_client_fallback.py +179 -0
  73. iicp_proxy-0.2.0/tests/test_ollama_cip_dispatch.py +104 -0
  74. iicp_proxy-0.2.0/tests/test_ollama_compat.py +117 -0
  75. iicp_proxy-0.2.0/tests/test_openai_cip_dispatch.py +296 -0
  76. iicp_proxy-0.2.0/tests/test_otel_proxy_spans.py +174 -0
  77. iicp_proxy-0.2.0/tests/test_peer_cache.py +66 -0
  78. iicp_proxy-0.2.0/tests/test_plugin.py +108 -0
  79. iicp_proxy-0.2.0/tests/test_prometheus_metrics.py +55 -0
  80. iicp_proxy-0.2.0/tests/test_proxy_config.py +133 -0
  81. iicp_proxy-0.2.0/tests/test_replica_registry.py +171 -0
  82. iicp_proxy-0.2.0/tests/test_replica_sig_verifier.py +153 -0
  83. iicp_proxy-0.2.0/tests/test_retry.py +100 -0
  84. iicp_proxy-0.2.0/tests/test_router.py +236 -0
  85. iicp_proxy-0.2.0/tests/test_selector.py +266 -0
  86. iicp_proxy-0.2.0/tests/test_translator.py +30 -0
  87. iicp_proxy-0.2.0/tests/test_trust_resolver.py +136 -0
@@ -0,0 +1,23 @@
1
+ Metadata-Version: 2.4
2
+ Name: iicp-proxy
3
+ Version: 0.2.0
4
+ Summary: IICP Client Proxy — Client Plane
5
+ License: Apache-2.0
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: fastapi>=0.115
8
+ Requires-Dist: uvicorn[standard]>=0.32
9
+ Requires-Dist: httpx>=0.27
10
+ Requires-Dist: pydantic>=2.9
11
+ Requires-Dist: pydantic-settings>=2.6
12
+ Requires-Dist: prometheus-client>=0.21
13
+ Requires-Dist: cryptography>=42
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest>=8.3; extra == "dev"
16
+ Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
17
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
18
+ Requires-Dist: respx>=0.21; extra == "dev"
19
+ Requires-Dist: ruff>=0.8; extra == "dev"
20
+ Requires-Dist: coverage[toml]>=7.6; extra == "dev"
21
+ Requires-Dist: cbor2>=5.4; extra == "dev"
22
+ Provides-Extra: cbor
23
+ Requires-Dist: cbor2>=5.4; extra == "cbor"
@@ -0,0 +1,224 @@
1
+ # IICP Client Proxy
2
+
3
+ **Plane**: Client
4
+ **Language**: Python 3.11+ / FastAPI
5
+ **ADRs**: [ADR-001](../project/decisions/ADR-001-three-plane-architecture.md), [ADR-005](../project/decisions/ADR-005-adapter-proxy-python-fastapi.md), [ADR-008](../project/decisions/ADR-008-node-scoring-formula.md)
6
+
7
+ Routes IICP task requests to discovered adapter nodes. Exposes three inbound LLM API surfaces on port 9483 so existing client tools work without modification:
8
+
9
+ | Surface | Endpoints | Compatible tools |
10
+ |---------|-----------|-----------------|
11
+ | OpenAI-compat | `POST /v1/chat/completions` | OpenAI SDK, LangChain, LlamaIndex, liteLLM, Cursor |
12
+ | Ollama-compat | `POST /api/chat`, `POST /api/generate`, `GET /api/tags`, `GET /api/version` | Open WebUI, Continue.dev, aider, Jan, Chatbox |
13
+ | Anthropic-compat | `POST /v1/messages`, `GET /v1/models` | Anthropic SDK (`base_url` override) |
14
+
15
+ All three surfaces translate to the same IICP `CALL` message and route through the same FallbackChain pipeline.
16
+
17
+ The proxy **does not re-rank nodes** — it trusts the directory's score order and only filters out unavailable nodes (ADR-008 hard rule: score computed server-side only).
18
+
19
+ ---
20
+
21
+ ## Module layout
22
+
23
+ ```
24
+ src/proxy/
25
+ main.py — FastAPI app factory, startup; wires routing stack + mounts compat apps
26
+ config.py — ProxyConfig (pydantic-settings)
27
+ clients/
28
+ directory.py — DirectoryClient: GET /v1/discover, GET /v1/bootstrap
29
+ node.py — NodeClient: POST /v1/task to a specific adapter node
30
+ routing/
31
+ selector.py — NodeSelector: filter-only (available=true); preserves directory order
32
+ router.py — TaskRouter: submit task to a single node via NodeClient
33
+ retry.py — RetryManager: 3 attempts, 200ms base, ×2 backoff, ±20% jitter
34
+ circuit_breaker.py — CircuitBreaker: 5 failures → open, 30s half-open (per node)
35
+ fallback.py — FallbackChain: best → second → local fallback → structured error
36
+ aggregator.py — ResultAggregator: best-of-N / majority vote (Phase 2)
37
+ openai_compat/ — OpenAI API surface (POST /v1/chat/completions)
38
+ server.py — create_compat_app(): base FastAPI app; other surfaces add routes to it
39
+ translator.py — OpenAI body → IicpTask, IicpResponse → OpenAI response
40
+ ollama_compat/ — Ollama API surface (/api/chat, /api/generate, /api/tags, /api/version)
41
+ server.py — add_ollama_routes(app): registers Ollama endpoints on the shared compat app
42
+ translator.py — Ollama body → IicpTask, IicpResponse → Ollama response
43
+ anthropic_compat/ — Anthropic Messages API surface (/v1/messages, /v1/models)
44
+ server.py — add_anthropic_routes(app): registers Anthropic endpoints on the shared compat app
45
+ translator.py — Anthropic body → IicpTask, IicpResponse → Anthropic response
46
+ plugin/
47
+ iicp_provider.py — IicpModelProvider: drop-in OpenAI-compatible provider for host assistants
48
+ (OpenClaw, Moltbot etc.); injects directory + selector + fallback_chain
49
+ network/
50
+ peer_cache.py — Local peer cache (Phase 2: persist discovered peers)
51
+ auth/
52
+ secrets.py — Node token storage (OS keychain integration)
53
+ cip/
54
+ consumer.py — CIPConsumerConfig + CIPCallFields (S.12 §4.1 wire-boundary validator:
55
+ policy ∈ {best_of_n,majority_vote,map_reduce}, replicas ∈ [1,10])
56
+ aggregation.py — CIPAggregationResult (S.12 §4.3): coordinator RESPONSE aggregation
57
+ object; cross-field invariants: responded ≤ dispatched,
58
+ selected_worker_id null when responded == 0
59
+ coordinator.py — CIP coordinator: split task, collect results from workers
60
+ dispatch.py — CIP dispatch helpers: worker selection + envelope construction
61
+ strategies.py — Split/merge strategies (token-split, prompt-prefix, local-first precedence S.12 §2.2)
62
+ address_state.py — AddressState: tracks observed source IP across directory interactions (ADR-011 DIR-ADDR-04)
63
+ metrics.py — Shared Prometheus metric definitions (iicp_proxy_tasks_total etc.)
64
+ otel_tracer.py — OTelTracer: W3C traceparent propagation, ADR-014 TRACE-04, no-op fallback
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Configuration
70
+
71
+ Environment variables (no TOML file for proxy — secrets should not live in files):
72
+
73
+ | Env var | Default | Description |
74
+ |---------|---------|-------------|
75
+ | `IICP_PROXY_DIRECTORY_URL` | `"https://iicp.network/api"` | Directory service URL |
76
+ | `IICP_NODE_TOKEN` | — | Bearer token for authenticating to adapter nodes |
77
+ | `IICP_PROXY_PREFERRED_REGION` | — | Hint passed to directory `?region=` filter |
78
+ | `IICP_PROXY_MAX_RETRIES` | `3` | Maximum task retry attempts |
79
+ | `IICP_PROXY_DIRECTORY_TIMEOUT_MS` | `5000` | Directory request timeout (ms) |
80
+ | `IICP_PROXY_HOST` | `"127.0.0.1"` | Listen address (loopback only by default) |
81
+ | `IICP_PROXY_PORT` | `9483` | Listen port (reserved IICP proxy band; set `=11434` for Ollama drop-in) |
82
+
83
+ ---
84
+
85
+ ## Running
86
+
87
+ ```bash
88
+ # Install
89
+ pip install -e ".[dev]"
90
+
91
+ # Run (listens on 127.0.0.1:9483 by default)
92
+ iicp-proxy
93
+
94
+ # Override the port — e.g. 11434 for literal Ollama drop-in
95
+ IICP_PROXY_PORT=11434 iicp-proxy
96
+
97
+ # Docker
98
+ docker build -t iicp-proxy .
99
+ docker run -p 127.0.0.1:9483:9483 --env-file .env iicp-proxy
100
+ ```
101
+
102
+ ---
103
+
104
+ ## Protocol surface usage
105
+
106
+ **OpenAI-compat** (ChatGPT SDK, LangChain, liteLLM — point `base_url` at port 9483):
107
+ ```bash
108
+ curl http://localhost:9483/v1/chat/completions \
109
+ -H "Content-Type: application/json" \
110
+ -d '{"model":"iicp","messages":[{"role":"user","content":"Hello"}]}'
111
+ ```
112
+
113
+ **Ollama-compat** (Open WebUI, Continue.dev, aider — set Ollama URL to `http://localhost:9483`):
114
+ ```bash
115
+ curl http://localhost:9483/api/chat \
116
+ -H "Content-Type: application/json" \
117
+ -d '{"model":"iicp","messages":[{"role":"user","content":"Hello"}]}'
118
+ ```
119
+
120
+ **Anthropic-compat** (Anthropic SDK — pass `base_url="http://localhost:9483"`):
121
+ ```bash
122
+ curl http://localhost:9483/v1/messages \
123
+ -H "Content-Type: application/json" \
124
+ -H "x-api-key: any" \
125
+ -H "anthropic-version: 2023-06-01" \
126
+ -d '{"model":"claude-3-5-sonnet-20241022","max_tokens":100,"messages":[{"role":"user","content":"Hello"}]}'
127
+ ```
128
+
129
+ All three translate to the same IICP `CALL` message with intent `urn:iicp:intent:llm:chat:v1`.
130
+
131
+ ---
132
+
133
+ ## Testing
134
+
135
+ ```bash
136
+ pytest tests/ -v
137
+ pytest tests/ --cov=proxy --cov-report=term-missing
138
+ ```
139
+
140
+ ---
141
+
142
+ ## Adding a new LLM API compat layer
143
+
144
+ The proxy translates between client-side LLM API formats and the IICP task wire format.
145
+ `openai_compat/` is the reference implementation; `ollama_compat/` and `anthropic_compat/`
146
+ are working examples. To add support for a new API shape, follow this pattern:
147
+
148
+ **1. Create `src/proxy/<name>_compat/`**
149
+
150
+ ```
151
+ <name>_compat/
152
+ __init__.py — empty
153
+ server.py — add_<name>_routes(app): registers endpoints on the shared compat app
154
+ translator.py — your_body → IicpTask, IicpResponse → your_response
155
+ ```
156
+
157
+ `translator.py` must export:
158
+ - `to_iicp_task(body: dict) -> tuple[UUID, str, dict]` — produces `(task_id, intent, payload)`
159
+ - `to_<name>_response(iicp_response: dict, ...) -> dict` — converts IICP response to your format
160
+
161
+ Use `ollama_compat/translator.py` as the template — it is ≤50 lines and handles the common
162
+ patterns (flatten messages, forward options, map token counts).
163
+
164
+ **2. Wire it in `main.py`**
165
+
166
+ ```python
167
+ from proxy.<name>_compat.server import add_<name>_routes
168
+
169
+ # In create_app(), after create_compat_app():
170
+ compat = create_compat_app()
171
+ add_ollama_routes(compat)
172
+ add_anthropic_routes(compat)
173
+ add_<name>_routes(compat) # add your surface here
174
+ app.mount("/", compat)
175
+ ```
176
+
177
+ The routing stack (`selector`, `fallback_chain`, `aggregator`, `directory`) is attached
178
+ to `app.state` during lifespan startup. In your handler:
179
+
180
+ ```python
181
+ @app.post("/your-endpoint")
182
+ async def your_handler(request: Request) -> JSONResponse:
183
+ fallback_chain = request.app.state.fallback_chain
184
+ directory = request.app.state.directory
185
+ selector = request.app.state.selector
186
+
187
+ body = await request.json()
188
+ task_id, intent, payload = your_translator.to_iicp_task(body)
189
+ nodes = selector.select(await directory.discover(intent=intent))
190
+ response = await fallback_chain.execute(nodes, task_id, intent, payload, timeout_ms=30_000)
191
+ return JSONResponse(content=your_translator.to_your_response(response))
192
+ ```
193
+
194
+ **3. No changes to routing, selector, or circuit breaker are needed.**
195
+
196
+ The routing layer is format-agnostic. The only contract: produce a valid IICP
197
+ `intent` URN and a `payload` dict; get back a response dict with a `result` key.
198
+
199
+ ---
200
+
201
+ ## CIP Wire Format Validation (CIP-V01..V06 — S.12)
202
+
203
+ The `cip/` package enforces all normative wire constraints from S.12 before any
204
+ dispatch occurs. Invalid values raise `ValidationError` at parse time → 422 IICP-E028.
205
+
206
+ | Model | File | Validates |
207
+ |-------|------|-----------|
208
+ | `CIPCallFields` | `cip/consumer.py` | `cip.policy` ∈ `{best_of_n, majority_vote, map_reduce}` (V01); `cip.replicas` ∈ [1, 10] (V02) |
209
+ | `CIPAggregationResult` | `cip/aggregation.py` | coordinator RESPONSE `cip_aggregation` object (V06): `replicas_responded` ≤ `replicas_dispatched`; `selected_worker_id` null when `replicas_responded == 0` |
210
+
211
+ `CIPConsumerConfig` (also in `consumer.py`) is operator-level config loaded from
212
+ `proxy.toml` — it silently clamps rather than rejecting. This is intentional: operator
213
+ config is trusted; per-request wire input is not.
214
+
215
+ ---
216
+
217
+ ## Key invariants
218
+
219
+ - Proxy binds to `127.0.0.1` only (loopback) — never exposed directly to internet.
220
+ - TLS certificate validation is **not** bypassable; no `verify=False` anywhere.
221
+ - Node score order from directory is preserved; only `available=false` nodes are filtered.
222
+ - Circuit breaker state is per-node and in-memory; resets on proxy restart (accepted Phase 1 limitation).
223
+ - No task payload content is logged (privacy invariant — mirrors adapter rule).
224
+ - All retries use jittered backoff; `RetryManager` enforces maximum 3 attempts.
@@ -0,0 +1,59 @@
1
+ [project]
2
+ name = "iicp-proxy"
3
+ version = "0.2.0"
4
+ description = "IICP Client Proxy — Client Plane"
5
+ license = {text = "Apache-2.0"}
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "fastapi>=0.115",
9
+ "uvicorn[standard]>=0.32",
10
+ "httpx>=0.27",
11
+ "pydantic>=2.9",
12
+ "pydantic-settings>=2.6",
13
+ "prometheus-client>=0.21",
14
+ # P6-4.2b: Ed25519 verification for X-IICP-Replica-Sig on discover responses.
15
+ # Pure-Python alternatives exist but are 50x slower; cryptography is well-
16
+ # maintained and widely deployed.
17
+ "cryptography>=42",
18
+ ]
19
+
20
+ [project.optional-dependencies]
21
+ dev = [
22
+ "pytest>=8.3",
23
+ "pytest-asyncio>=0.24",
24
+ "pytest-cov>=5.0",
25
+ "respx>=0.21",
26
+ "ruff>=0.8",
27
+ "coverage[toml]>=7.6",
28
+ "cbor2>=5.4",
29
+ ]
30
+ cbor = [
31
+ "cbor2>=5.4",
32
+ ]
33
+
34
+ [project.scripts]
35
+ iicp-proxy = "proxy.main:run"
36
+
37
+ [build-system]
38
+ requires = ["setuptools>=68"]
39
+ build-backend = "setuptools.build_meta"
40
+
41
+ [tool.setuptools.packages.find]
42
+ where = ["src"]
43
+
44
+ [tool.pytest.ini_options]
45
+ asyncio_mode = "auto"
46
+ testpaths = ["tests"]
47
+
48
+ [tool.ruff]
49
+ line-length = 120
50
+ target-version = "py311"
51
+
52
+ [tool.ruff.lint]
53
+ select = ["E", "F", "I", "UP"]
54
+
55
+ [tool.coverage.run]
56
+ source = ["src/proxy"]
57
+ omit = ["tests/*"]
58
+ [tool.coverage.report]
59
+ fail_under = 70
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,23 @@
1
+ Metadata-Version: 2.4
2
+ Name: iicp-proxy
3
+ Version: 0.2.0
4
+ Summary: IICP Client Proxy — Client Plane
5
+ License: Apache-2.0
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: fastapi>=0.115
8
+ Requires-Dist: uvicorn[standard]>=0.32
9
+ Requires-Dist: httpx>=0.27
10
+ Requires-Dist: pydantic>=2.9
11
+ Requires-Dist: pydantic-settings>=2.6
12
+ Requires-Dist: prometheus-client>=0.21
13
+ Requires-Dist: cryptography>=42
14
+ Provides-Extra: dev
15
+ Requires-Dist: pytest>=8.3; extra == "dev"
16
+ Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
17
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
18
+ Requires-Dist: respx>=0.21; extra == "dev"
19
+ Requires-Dist: ruff>=0.8; extra == "dev"
20
+ Requires-Dist: coverage[toml]>=7.6; extra == "dev"
21
+ Requires-Dist: cbor2>=5.4; extra == "dev"
22
+ Provides-Extra: cbor
23
+ Requires-Dist: cbor2>=5.4; extra == "cbor"
@@ -0,0 +1,85 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/iicp_proxy.egg-info/PKG-INFO
4
+ src/iicp_proxy.egg-info/SOURCES.txt
5
+ src/iicp_proxy.egg-info/dependency_links.txt
6
+ src/iicp_proxy.egg-info/entry_points.txt
7
+ src/iicp_proxy.egg-info/requires.txt
8
+ src/iicp_proxy.egg-info/top_level.txt
9
+ src/proxy/__init__.py
10
+ src/proxy/address_state.py
11
+ src/proxy/config.py
12
+ src/proxy/main.py
13
+ src/proxy/metrics.py
14
+ src/proxy/otel_tracer.py
15
+ src/proxy/anthropic_compat/__init__.py
16
+ src/proxy/anthropic_compat/server.py
17
+ src/proxy/anthropic_compat/translator.py
18
+ src/proxy/auth/__init__.py
19
+ src/proxy/auth/secrets.py
20
+ src/proxy/cip/__init__.py
21
+ src/proxy/cip/aggregation.py
22
+ src/proxy/cip/consumer.py
23
+ src/proxy/cip/coordinator.py
24
+ src/proxy/cip/dispatch.py
25
+ src/proxy/cip/gates.py
26
+ src/proxy/cip/receipts.py
27
+ src/proxy/cip/strategies.py
28
+ src/proxy/clients/__init__.py
29
+ src/proxy/clients/did_resolver.py
30
+ src/proxy/clients/directory.py
31
+ src/proxy/clients/node.py
32
+ src/proxy/clients/replica_registry.py
33
+ src/proxy/clients/replica_sig_verifier.py
34
+ src/proxy/clients/trust_resolver.py
35
+ src/proxy/network/__init__.py
36
+ src/proxy/network/peer_cache.py
37
+ src/proxy/ollama_compat/__init__.py
38
+ src/proxy/ollama_compat/server.py
39
+ src/proxy/ollama_compat/translator.py
40
+ src/proxy/openai_compat/__init__.py
41
+ src/proxy/openai_compat/server.py
42
+ src/proxy/openai_compat/translator.py
43
+ src/proxy/plugin/__init__.py
44
+ src/proxy/plugin/iicp_provider.py
45
+ src/proxy/routing/__init__.py
46
+ src/proxy/routing/aggregator.py
47
+ src/proxy/routing/circuit_breaker.py
48
+ src/proxy/routing/fallback.py
49
+ src/proxy/routing/retry.py
50
+ src/proxy/routing/router.py
51
+ src/proxy/routing/selector.py
52
+ tests/test_address_learning.py
53
+ tests/test_aggregator.py
54
+ tests/test_anthropic_cip_dispatch.py
55
+ tests/test_anthropic_compat.py
56
+ tests/test_cip_aggregation.py
57
+ tests/test_cip_call_validators.py
58
+ tests/test_cip_consumer.py
59
+ tests/test_cip_coordinator.py
60
+ tests/test_cip_integration.py
61
+ tests/test_circuit_breaker.py
62
+ tests/test_compat_routes.py
63
+ tests/test_coverage_gates.py
64
+ tests/test_did_resolver.py
65
+ tests/test_directory_client.py
66
+ tests/test_directory_redirect_trust.py
67
+ tests/test_directory_sig_verify.py
68
+ tests/test_fallback.py
69
+ tests/test_node_client.py
70
+ tests/test_node_client_fallback.py
71
+ tests/test_ollama_cip_dispatch.py
72
+ tests/test_ollama_compat.py
73
+ tests/test_openai_cip_dispatch.py
74
+ tests/test_otel_proxy_spans.py
75
+ tests/test_peer_cache.py
76
+ tests/test_plugin.py
77
+ tests/test_prometheus_metrics.py
78
+ tests/test_proxy_config.py
79
+ tests/test_replica_registry.py
80
+ tests/test_replica_sig_verifier.py
81
+ tests/test_retry.py
82
+ tests/test_router.py
83
+ tests/test_selector.py
84
+ tests/test_translator.py
85
+ tests/test_trust_resolver.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ iicp-proxy = proxy.main:run
@@ -0,0 +1,19 @@
1
+ fastapi>=0.115
2
+ uvicorn[standard]>=0.32
3
+ httpx>=0.27
4
+ pydantic>=2.9
5
+ pydantic-settings>=2.6
6
+ prometheus-client>=0.21
7
+ cryptography>=42
8
+
9
+ [cbor]
10
+ cbor2>=5.4
11
+
12
+ [dev]
13
+ pytest>=8.3
14
+ pytest-asyncio>=0.24
15
+ pytest-cov>=5.0
16
+ respx>=0.21
17
+ ruff>=0.8
18
+ coverage[toml]>=7.6
19
+ cbor2>=5.4
@@ -0,0 +1,2 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """IICP client proxy — routes tasks to discovered adapter nodes."""
@@ -0,0 +1,29 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Runtime holder for the proxy's observed external IP (DIR-ADDR-02)."""
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+
7
+
8
+ @dataclass
9
+ class AddressState:
10
+ observed_source_ip: str | None = field(default=None)
11
+ endpoint: str | None = field(default=None)
12
+ node_id: str | None = field(default=None)
13
+
14
+ def update_from_ack(self, ack: dict) -> None:
15
+ self.observed_source_ip = ack.get("observed_source_ip")
16
+ self.node_id = ack.get("node_id")
17
+
18
+ def update_from_me(self, me: dict) -> None:
19
+ self.observed_source_ip = me.get("observed_source_ip")
20
+ self.node_id = me.get("node_id")
21
+ self.endpoint = me.get("endpoint")
22
+
23
+
24
+ # Module-level singleton — set once at startup, read by status commands
25
+ _state: AddressState = AddressState()
26
+
27
+
28
+ def get_address_state() -> AddressState:
29
+ return _state
@@ -0,0 +1 @@
1
+ # SPDX-License-Identifier: Apache-2.0