Skip to content

[DEV-2042] Docs: Fliplet.App.V3.Actions.request() for server actions - #306

Open
Arpanexe wants to merge 5 commits into
masterfrom
feature/DEV-2042-server-http-transport
Open

Arpanexe wants to merge 5 commits into
masterfrom
feature/DEV-2042-server-http-transport

Conversation

@Arpanexe

@Arpanexe Arpanexe commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Product areas affected

Developer docs (developers.fliplet.com) — App Actions V3 and the V3 app settings page. No CLI code changes.

What does this PR do?

Documents Fliplet.App.V3.Actions.request(), the new way for a server action to call a provider API that rejects cross-origin browser requests (CORS).

  • docs/API/core/app-actions-v3.md: new request() section (options, response shape, limits, error codes, what is and isn't logged). The context.settings example now uses request() instead of fetch. The two warnings that said server-side HTTP isn't available, and that a key-in-the-query-string provider can't be called, are updated.
  • docs/API/v3/app-settings.md: points to request() from the "Reading settings in a server action" section.
  • Regenerated docs/.well-known/llms-full.txt and llms-v3-libraries.json.

JIRA ticket

DEV-2042

Result

  • node bin/build-agent-indexes.mjs --strict: passes.
  • npm run test:unit: 163/163 passing.

Checklist

  • Added automated test coverage as appropriate for this change.

Docs only, so no new tests.

Deployment instructions

Don't merge until the feature is live in production. Merging publishes the docs, and Studio's AI builder reads them, so it would start generating request() calls that don't work yet.

Order: Fliplet/fliplet-service-browser#53, then Fliplet/fliplet-api#8732, then this.

Author concerns

  • bin/check-v3-catalog-drift.mjs reports 1 error (fliplet-service-worker not in /v1/widgets/assets). It's the same on untouched master, so it isn't from this PR.
  • build-agent-indexes.mjs does nothing when run on Windows (its "run as script" check compares import.meta.url with a Windows path and never matches). I ran it in a Linux container instead. Worth a separate small fix.
  • The limits and error codes here mirror the runner PR. If review changes them there, this needs the same edit.

🤖 Generated with Claude Code

Replaces the 'server-side HTTP is not available yet' caveat with the
request() reference: options, response shape, limits, error codes and
the no-logging guarantee. Regenerated the AI-doc indexes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Deploying fliplet-cli with  Cloudflare Pages  Cloudflare Pages

Latest commit: 9b8f8ee
Status: ✅  Deploy successful!
Preview URL: https://bc54bc95.fliplet-cli.pages.dev
Branch Preview URL: https://feature-dev-2042-server-http.fliplet-cli.pages.dev

View logs

@zeryabkhan91 zeryabkhan91 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review (via /fliplet-pr-review)

Review basis: subject=e7d2bb8e46049994b4a44f176c465b0cad541131 standards=fliplet-engineering@0.18.0 revision=11c68cdf718d75681ad0fc323e57b80bb36e2fa9 source=fliplet-pr-review digest=sha256:40c49b60a8bd5384

Summary: Documents Fliplet.App.V3.Actions.request() and regenerates the LLM index files.

Regression risk: Docs only. Merging publishes and the AI builder reads these docs, so the release-order note in the description applies.

Acceptance criteria: The limits, error codes and response shape match the runner code in Fliplet/fliplet-service-browser#53, and the new in-page anchor resolves on the Cloudflare preview.

CI: Green, but the PR currently conflicts with master.

Two things to address:

  1. Merge conflict. The only conflict is the generatedAt line in docs/.well-known/llms-v3-libraries.json (master moved on 5 October). Merge master and re-run build-agent-indexes.mjs.
  2. One claim is not true of the runner as it stands, see the inline comment.

- **The body is text.** Use `JSON.parse(response.body)` for a JSON response. Binary responses are not supported.
- **HTTPS only, public destinations only.** Private, loopback and link-local addresses are refused, including when a public hostname resolves or redirects to one.
- **Limits.** 30 seconds by default (50 at most) for the whole request including redirects, 5 MB for the response body and for the request body, and 5 redirects.
- **Redirects to another host drop every request header**, so a credential is never forwarded to a host you did not name. Call the final URL directly if the provider needs the header there.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"a credential is never forwarded to a host you did not name" is not true today for a credential in the body.

The runner drops headers on a cross-host redirect, but on a 307/308 it resends the request body to the new host (details on Fliplet/fliplet-service-browser#53). This page also tells authors a credential can go "in a header (or the body)".

Either the runner stops resending the body, in which case this sentence can stay, or it needs softening to headers only. docs/.well-known/llms-full.txt carries the same text, so regenerate after the edit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The runner no longer resends the body (Fliplet/fliplet-service-browser#53, e231719), so the sentence holds now. I added a bullet that spells out the 307/308 case so it isn't a surprise: the request resolves with the 3xx and its location.

Also in d1e9458: the body is always decoded as UTF-8 (other encodings not supported), compressed responses are decompressed, master merged and the index files regenerated.

galisufyan-327
galisufyan-327 previously approved these changes Oct 6, 2026

@galisufyan-327 galisufyan-327 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Arpanexe and others added 2 commits October 6, 2026 13:16
…rver-http-transport

# Conflicts:
#	docs/.well-known/llms-v3-libraries.json
Follows the runner review fixes: a 307/308 to another host that would
resend the body is not followed, the body is always decoded as UTF-8,
and compressed responses are decompressed. Regenerated the AI-doc indexes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
zeryabkhan91
zeryabkhan91 previously approved these changes Oct 6, 2026

@zeryabkhan91 zeryabkhan91 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

# Conflicts:
#	docs/.well-known/llms-v3-libraries.json
QA found the runner's Chromium runs with web security off (@sparticuz/chromium
default), so fetch() already reaches CORS-less providers. Reword: request()
is the call that refuses private addresses, caps limits and stays out of the
logs. An any action run on the server now also gets UNAVAILABLE.

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

@zeryabkhan91 zeryabkhan91 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review (via /fliplet-pr-review)

Review basis: subject=9b8f8ee57c480e9dbba97e450b031fa51de50914 standards=fliplet-engineering@0.18.0 revision=11c68cdf718d75681ad0fc323e57b80bb36e2fa9 source=fliplet-pr-review digest=sha256:40c49b60a8bd5384

Approving. Both points from my last review are sorted: the conflict is gone, and the 307/308 body case is documented and matches the runner now. Checked llms-full.txt too, it has the new wording and none of the old CORS lines.

A few non-blocking comments inline. One more for the description: the deploy order still says #53, then #8732, then this. The any action line only holds once api #8790 and runner #56 are live (and studio #9041 for the AI guidance), so worth adding those to the order. The body also still mentions regenerating llms-v3-libraries.json, which isn't in the diff any more.

<p class="warning"><strong>Never let a setting value leave the action.</strong> Never return it, never log it (<code>console.*</code>), never put it in a thrown or returned error message, and never put it in a URL or query string — send it in a request header or body instead. Return values and error messages are stored in the app action logs and returned to the caller of <code>runWithResult()</code>; console output and the URL of every <code>fetch</code> or XHR request are captured in the server runner logs. A provider that only accepts its key in the URL query string must be called with <a href="#flipletappv3actionsrequest-server-actions-only"><code>Fliplet.App.V3.Actions.request()</code></a>, which is the one request the runner does not log.</p>

<p class="warning">Server actions run in a headless browser, so <code>fetch</code> from a server action is a cross-origin browser request. Provider APIs that do not allow cross-origin browser requests (CORS) reject it. Server-side HTTP requests without this restriction are not available yet.</p>
<p class="warning">Call third-party provider APIs from a server action with <a href="#flipletappv3actionsrequest-server-actions-only"><code>Fliplet.App.V3.Actions.request()</code></a>, not <code>fetch</code>. Both reach the provider, but only <code>request()</code> refuses private network addresses, caps the timeout, response size and redirects, and keeps the URL, headers and body out of the server runner logs. The URL of every <code>fetch</code> is captured in those logs.</p>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: this repo and developers.fliplet.com are public, and this line spells out that a server action's own fetch() isn't address-filtered. QA saw it try to connect to 10.0.0.1 inside the runner's VPC. I'd just say what request() does without comparing it to fetch:

Suggested change
<p class="warning">Call third-party provider APIs from a server action with <a href="#flipletappv3actionsrequest-server-actions-only"><code>Fliplet.App.V3.Actions.request()</code></a>, not <code>fetch</code>. Both reach the provider, but only <code>request()</code> refuses private network addresses, caps the timeout, response size and redirects, and keeps the URL, headers and body out of the server runner logs. The URL of every <code>fetch</code> is captured in those logs.</p>
<p class="warning">Call third-party provider APIs from a server action with <a href="#flipletappv3actionsrequest-server-actions-only"><code>Fliplet.App.V3.Actions.request()</code></a>. It refuses private network addresses, caps the timeout, response size and redirects, and keeps the URL, headers and body out of the server runner logs. The URL of every <code>fetch</code> is captured in those logs.</p>

Same idea at L328 and in app-settings.md, then regenerate llms-full.txt. The VPC reachability itself needs its own ticket, not something for this PR.


#### `Fliplet.App.V3.Actions.request()` (server actions only)

`Fliplet.App.V3.Actions.request()` makes an HTTPS request from the server runner instead of from the headless browser the action runs in. Unlike `fetch`, it refuses private network addresses, enforces timeout, size and redirect limits, and is never written to the runner logs. The request leaves from the same static IP address as every other server action request, so provider keys restricted to an allow-listed IP keep working.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, same as L270:

Suggested change
`Fliplet.App.V3.Actions.request()` makes an HTTPS request from the server runner instead of from the headless browser the action runs in. Unlike `fetch`, it refuses private network addresses, enforces timeout, size and redirect limits, and is never written to the runner logs. The request leaves from the same static IP address as every other server action request, so provider keys restricted to an allow-listed IP keep working.
`Fliplet.App.V3.Actions.request()` makes an HTTPS request from the server runner instead of from the headless browser the action runs in. It refuses private network addresses, enforces timeout, size and redirect limits, and is never written to the runner logs. The request leaves from the same static IP address as every other server action request, so provider keys restricted to an allow-listed IP keep working.

| `TIMEOUT` | No complete response within `timeoutMs` |
| `TOO_LARGE` | Response body over 5 MB |
| `TOO_MANY_REDIRECTS` | More than 5 redirects |
| `NETWORK_ERROR` | DNS, connection or TLS failure |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: QA saw a header value with a CRLF in it come back as NETWORK_ERROR, but this table points malformed input at INVALID_REQUEST. Either mention invalid header values here or map them to INVALID_REQUEST in the runner.

Comment on lines 109 to +110

- Call the provider with [`Fliplet.App.V3.Actions.request()`](../core/app-actions-v3#flipletappv3actionsrequest-server-actions-only). Unlike a plain `fetch`, it refuses private network addresses, caps the timeout, response size and redirects, and keeps the URL, headers and body out of the runner logs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: the blank line above makes this a separate list from the two bullets before it. And same wording point as app-actions-v3.md L270, I'd drop the comparison with fetch:

Suggested change
- Call the provider with [`Fliplet.App.V3.Actions.request()`](../core/app-actions-v3#flipletappv3actionsrequest-server-actions-only). Unlike a plain `fetch`, it refuses private network addresses, caps the timeout, response size and redirects, and keeps the URL, headers and body out of the runner logs.
- Call the provider with [`Fliplet.App.V3.Actions.request()`](../core/app-actions-v3#flipletappv3actionsrequest-server-actions-only). It refuses private network addresses, caps the timeout, response size and redirects, and keeps the URL, headers and body out of the runner logs.

This branch has not been deployed

No deployments
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.

3 participants