Skip to content

[M3a] Cloudflare Worker target: OpenRouter provider and an allowlisted http tool - #295

Merged
LinuxDevil merged 5 commits into
mainfrom
lou-m3a-worker-openrouter-http
Oct 2, 2026
Merged

LinuxDevil merged 5 commits into
mainfrom
lou-m3a-worker-openrouter-http

Conversation

@LinuxDevil

Copy link
Copy Markdown
Owner

Closes #223

Round 2 ticket M3a. A spec built with lousho build --target=cloudflare-worker can now use the openrouter provider and the http tool.

What changed

  • OpenRouter on Workers. runtime.worker.ts registers OpenRouterProvider. It imports the provider directly, not through the barrel. The key comes from the OPENROUTER_API_KEY binding. WORKER_SUPPORTED_PROVIDERS is now ['mock','openai','anthropic','openrouter']. The OpenRouter bundle passes the node: leak check with no import fixes and no new shims; the bundle test proves it. ollama stays unsupported.

  • src/tools/built-in/httpCore.ts (new, Node-free). This is the shared input schema and description, the request with its timeout and caller cancellation, the manual redirect loop (an injected checkHop runs on every redirect target), the response reading, and ipFamily(), a regex replacement for net.isIP. A test checks it gives the same answer as node:net on 28 samples.

  • src/tools/built-in/workerHttp.ts (new). createWorkerHttpTool({ allow, timeout?, maxRedirects? }) has the same descriptor name (http_request) and the same input schema object as createHttpTool. It runs over the platform fetch(), which is injectable for tests. It has no requiresSandbox and no validateSSL. On the first URL and on every redirect it refuses:

    • any scheme but http: and https:;
    • an IP-literal host, even a listed one. This includes 2130706433 and 0x7f000001, which URL normalizes, and every bracketed IPv6;
    • a host that does not match allow, checked with matchesHost after lower-casing. *.x matches subdomains only. An empty list refuses everything.

    An entry that is not a host pattern fails every request with a message naming it. Every refusal and failure is a toolFailure. A caller's cancellation stays an AbortError.

  • Worker wiring. WORKER_TOOLS entries are now built per request from env. http reads the comma-separated LOUSHO_HTTP_ALLOW binding. WORKER_SUPPORTED_TOOLS is ['current-date','day-name','http']. When the spec lists http, wrangler.toml gets a commented # [vars] / # LOUSHO_HTTP_ALLOW = "api.example.com" block.

  • What the security claims stay within. The docs say what [N13a] web_fetch built-in with pinned-DNS SSRF checks; http_request no longer open to DNS rebinding #277 says: a Worker can check the URL and IP-literal hosts, but it cannot check where a host name connects. The allowlist moves that trust to the deployer. A listed name is reached wherever it resolves. A model cannot choose an arbitrary or DNS-rebinding host, because only listed names pass.

  • Docs.

    • docs/cloudflare-workers.md (the G6 page that merged during this ticket): limits table rows for providers and tools, the "Providers and tools" reasons list, the key and peer paragraph, a LOUSHO_HTTP_ALLOW row in the bindings table (the intro line "Its two bindings:" became "Its bindings:"), and a [vars] example.
    • docs/deployment.md: the summary list.
    • docs/providers.md: the "Where each provider runs" note.
    • docs/tools.md and docs/troubleshooting.md: the Worker sentences, which now link to cloudflare-workers.md#providers-and-tools.
    • No headings added, removed or renamed.
  • Tests.

    • workerHttp.test.ts (19 tests, no network).

    • cloudflare.test.ts:

      • the rejection test now uses web-fetch and ollama;
      • the provider and tool lists;
      • the wrangler.toml [vars] line;
      • OpenRouter registry and prepareWorkerSpec resolving http;
      • the it.each real-provider bundle build now includes openrouter.
    • it.each now replaces globalThis.fetch with a 401 stub for all three providers. It asserts that the request went to the provider's own host, so the test no longer touches the network.

    • A new build with provider: openrouter, tools: [http] checks the leak check and the wrangler.toml line. A scripted OpenRouter (an injected fetch answering chat completions with a tool call) drives the bundle's /chat:

      • refused when LOUSHO_HTTP_ALLOW is unset (and the target host is never requested);
      • refused for an unlisted host;
      • returns the body when the host is listed.

      All builds use withBuildLock.

    • src/schedules/scheduled.test.ts used tools: ['http'] as "a tool the Worker cannot build". It now uses web-fetch.

  • CHANGELOG entry under Unreleased / Added.

Notes:

  • The ticket asked for a scripted mock provider in the bundle test. The spec-level mock provider cannot be scripted from a spec: it only produces a tool call with { input: 'mock input' }. So the test scripts the bundled openrouter provider through an injected fetch. That also exercises OpenRouter end to end, offline.
  • createAgent names a spec tool after its spec key, on Node and on the Worker alike. So the model sees the tool as http, while the descriptor name is http_request. That behaviour existed before this change.

Verification (on the branch after merging origin/main at d244b1f)

npx tsc --noEmit                                         exit 0
npm run lint                                             exit 0 (zero warnings)
npm run build                                            exit 0
npm run build --workspace=packages/create-lousho-agent   exit 0
npm run test:types                                       11 files, 57 tests passed
npm run docs:verify-snippets -- --skip-build             all 207 snippets type-check; 8 run cleanly
npm run docs:llms:check                                  exit 0
npm run test:coverage                                    227 files passed, 3248 tests passed, 2 skipped
                                                         httpCore.ts 99.05% lines, workerHttp.ts 100% lines, http.ts 99.27% lines
npm run fallow                                           exit 0; dead files 0.0%, dead exports 0.0%, maintainability 89.7; 0 above threshold
npm run typecheck --workspace apps/agent-forge           exit 0
npm run typecheck:server --workspace apps/agent-forge    exit 0
npm run test --workspace apps/agent-forge -- --run       13 files, 112 tests passed
npm run test:server --workspace apps/agent-forge         14 files, 122 tests passed
npm run pack-smoke                                       all checks passed
npx vitest run src/deploy (alone)                        14 files, 107 tests passed (before the G6 merge; covered again by test:coverage after it)

Live test spend: none. The ticket has no live test, and no model or provider API was called. The provider bundle tests now stub fetch.

Docs site follow-up

  • No new pages from this pull request.
  • Edited, no heading changes: docs/cloudflare-workers.md (limits table, "Providers and tools" list, bindings table and its intro line, [vars] example), docs/deployment.md, docs/providers.md, docs/tools.md, docs/troubleshooting.md.

🤖 Generated with Claude Code

LinuxDevil and others added 5 commits October 2, 2026 19:09
…owlisted http tool

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…available on Workers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@LinuxDevil

Copy link
Copy Markdown
Owner Author

Merged origin/main again (d8539a7, fromAiSdk #287); CHANGELOG keeps both entries, llms regenerated. Re-ran the full verification on 333f3ae: tsc, lint, build, create-lousho-agent build, test:types, docs:verify-snippets, docs:llms:check all exit 0; test:coverage 228 files, 3260 passed / 2 skipped; fallow exit 0 (0 above threshold, dead exports 0.0%); Agent Forge typecheck, typecheck:server exit 0, test 112 passed, test:server 122 passed; pack-smoke all checks passed; npx vitest run src/deploy alone 15 files, 109 passed.

@LinuxDevil
LinuxDevil merged commit d457d1d into main Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[M3a] Cloudflare Worker target: OpenRouter provider and an allowlisted http tool

1 participant