Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions .github/workflows/release-clients.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Publish TypeScript / Python platform clients when their paths change on main.
# Publish TypeScript / Python / Java platform clients when their paths change on main.
#
# - push to main under clients/** → detect which client(s) changed and publish
# - workflow_dispatch → optional CLIENTS override (typescript|python|all)
# - workflow_dispatch → optional CLIENTS override (typescript|python|java|all)
#
# npm (OIDC trusted publishing — no NPM_TOKEN):
# https://www.npmjs.com/package/@afi-ai/platform-client/access
Expand All @@ -11,6 +11,11 @@
# Environment: leave blank (must match this workflow)
#
# Python: PYPI_API_TOKEN secret (or PyPI trusted publisher)
# Java: GitHub Packages via GITHUB_TOKEN (packages:write)
#
# Version bump PR (main is PR-only):
# Enable Actions → "Allow GitHub Actions to create and approve pull requests"
# OR set secret CLIENT_RELEASE_GH_TOKEN (PAT with contents + pull requests)

name: Release clients

Expand All @@ -22,6 +27,8 @@ on:
- '!clients/typescript/package.json'
- 'clients/python/**'
- '!clients/python/pyproject.toml'
- 'clients/java/**'
- '!clients/java/pom.xml'
- 'scripts/release-client-*.sh'
- 'scripts/release-clients.sh'
- 'scripts/open-client-version-pr.sh'
Expand All @@ -30,14 +37,15 @@ on:
workflow_dispatch:
inputs:
clients:
description: "Clients to release (typescript, python, or all)"
description: "Clients to release (typescript, python, java, or all)"
required: true
default: all
type: choice
options:
- all
- typescript
- python
- java
dry_run:
description: "Build and test without publishing"
required: false
Expand All @@ -48,6 +56,7 @@ permissions:
contents: write
id-token: write
pull-requests: write
packages: write

concurrency:
group: release-clients-${{ github.ref }}
Expand All @@ -63,6 +72,7 @@ jobs:
contents: write
id-token: write
pull-requests: write
packages: write
steps:
- uses: actions/checkout@v4
with:
Expand Down Expand Up @@ -106,6 +116,16 @@ jobs:
with:
python-version: "3.12"

- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
cache: maven
cache-dependency-path: clients/java/pom.xml
server-id: github
server-username: GITHUB_ACTOR
server-password: GITHUB_TOKEN

- name: Semver unit tests
run: bash scripts/semver_test.sh

Expand Down Expand Up @@ -142,6 +162,10 @@ jobs:
clients/python scripts/release-client-python.sh | grep -q .; then
clients+=(python)
fi
if git diff --name-only "${before}" HEAD -- \
clients/java scripts/release-client-java.sh | grep -q .; then
clients+=(java)
fi
if [[ ${#clients[@]} -eq 0 ]]; then
echo "clients=" >> "$GITHUB_OUTPUT"
else
Expand All @@ -153,6 +177,9 @@ jobs:
env:
PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
TWINE_USERNAME: __token__
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_ACTOR: ${{ github.actor }}
GITHUB_REPOSITORY: ${{ github.repository }}
DRY_RUN: ${{ steps.resolve.outputs.dry_run }}
COMMIT_BUMP: ${{ steps.resolve.outputs.commit_bump }}
CLIENTS: ${{ steps.resolve.outputs.clients }}
Expand All @@ -162,7 +189,10 @@ jobs:
- name: Push tags and open version bump PR
if: steps.resolve.outputs.clients != '' && steps.resolve.outputs.commit_bump == '1'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# GITHUB_TOKEN cannot create PRs unless the repo enables
# "Allow GitHub Actions to create and approve pull requests".
# Optional CLIENT_RELEASE_GH_TOKEN (PAT) bypasses that restriction.
GH_TOKEN: ${{ secrets.CLIENT_RELEASE_GH_TOKEN || secrets.GITHUB_TOKEN }}
GITHUB_REF_NAME: ${{ github.ref_name }}
run: bash scripts/open-client-version-pr.sh

Expand Down
11 changes: 11 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@ jobs:
with:
python-version: "3.12"

- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
cache: maven
cache-dependency-path: clients/java/pom.xml

- name: Install PyYAML
run: pip install pyyaml

Expand All @@ -142,3 +149,7 @@ jobs:
run: |
pip install -e ".[dev]"
pytest -q

- name: Java client tests
working-directory: clients/java
run: mvn -B -q test
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
quickstart deploy-init deploy-up deploy-down deploy-logs deploy-health \
deploy-infra deploy-controlplane deploy-dataplane deploy-worker deploy-web \
build-release build-images brand-assets \
release-clients release-client-typescript release-client-python \
release-clients release-client-typescript release-client-python release-client-java \
openapi-lint openapi-drift openapi-gen openapi-check \
proto-gen proto-check

Expand Down Expand Up @@ -79,6 +79,7 @@ build-release:
# Publish platform clients (npm / PyPI). Examples:
# DRY_RUN=1 make release-clients
# CLIENTS=typescript make release-client-typescript
# CLIENTS=java make release-client-java
# FORCE=1 CLIENTS=all make release-clients
release-clients:
bash scripts/release-clients.sh
Expand All @@ -89,6 +90,9 @@ release-client-typescript:
release-client-python:
bash scripts/release-client-python.sh

release-client-java:
bash scripts/release-client-java.sh

build-images:
@test -f $(DEPLOY_ENV) || (echo "missing $(DEPLOY_ENV) — run make deploy-init" >&2; exit 1)
docker compose -f $(DEPLOY_COMPOSE) --env-file $(DEPLOY_ENV) \
Expand Down Expand Up @@ -140,7 +144,7 @@ openapi-drift:
openapi-gen:
$(NPX) --yes openapi-typescript@7 api/openapi/platform.openapi.yaml -o clients/typescript/src/schema.gen.ts
@echo "Generated clients/typescript/src/schema.gen.ts"
@echo "Python client is hand-maintained against platform.openapi.yaml (see clients/python)."
@echo "Python and Java clients are hand-maintained against platform.openapi.yaml."

openapi-check: openapi-lint openapi-drift
$(MAKE) openapi-gen
Expand Down
37 changes: 28 additions & 9 deletions clients/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ Thin clients generated / maintained against [`../api/openapi/platform.openapi.ya
| ------- | -------- | ------- |
| [`typescript/`](typescript/) | TypeScript | `npm i @afi-ai/platform-client` |
| [`python/`](python/) | Python | `pip install afi-platform` |
| [`java/`](java/) | Java | `ai.afi:platform-client` ([GitHub Packages](https://github.com/curefatih/afi/packages)) |

Local development: `pnpm add ../clients/typescript` or `pip install -e clients/python`.
Local development: path install for TS/Python, or `mvn -f clients/java test`.

These are **not** the in-process extension SDKs under [`../sdk/`](../sdk/).

Expand All @@ -20,10 +21,11 @@ For **signed-request auth** (RFC 9421) instead of a virtual API key, use the sha
| Go | [`../sdk/httpsign`](../sdk/httpsign) — `SignRequest`, `Client` |
| Python | `afi_platform.sign_headers` |
| TypeScript | `signHeaders` from `@afi-ai/platform-client` |
| Java | `ai.afi.platform.HttpSign.signHeaders` |

## Releasing

Pushes to `main` that touch `clients/typescript` or `clients/python` run [`.github/workflows/release-clients.yml`](../.github/workflows/release-clients.yml). Each changed client is tested, versioned (patch auto-bump when the local version is already published), and published to npm / PyPI.
Pushes to `main` that touch `clients/typescript`, `clients/python`, or `clients/java` run [`.github/workflows/release-clients.yml`](../.github/workflows/release-clients.yml). Each changed client is tested, versioned (patch auto-bump when the local version is already published), and published to npm / PyPI / GitHub Packages.

### npm auth (TypeScript)

Expand All @@ -45,22 +47,39 @@ After a successful publish, CI pushes the release tag, opens
`chore/clients-version-bump`, and **merges it** (`gh pr merge --squash`).
Title includes `[skip release]` so merge does not republish.

If auto-merge fails under branch rules, enable **Settings → General → Allow
auto-merge**, and/or allow `github-actions[bot]` to bypass required
reviews/checks for that PR. The workflow falls back to `--auto` (merge when
checks go green).
**Required for PR create/merge with `GITHUB_TOKEN`:**

1. **Settings → Actions → General → Workflow permissions**
- Read and write permissions
- ✅ **Allow GitHub Actions to create and approve pull requests**

Or set secret `CLIENT_RELEASE_GH_TOKEN` to a fine-grained PAT (contents +
pull requests) / classic `repo` PAT — the workflow prefers that token.

If merge still fails under branch rules, enable **Allow auto-merge** and/or
allow the bot/PAT to bypass required reviews/checks.

If CI prints `ENEEDAUTH` or the “bypass 2FA” notice with no token configured, npm never completed the OIDC exchange — usually an empty `_authToken` in `$NPM_CONFIG_USERCONFIG`, a Trusted Publisher mismatch, or npm < 11.5.1.

### PyPI auth (Python)

Repo secret `PYPI_API_TOKEN` (or configure a PyPI trusted publisher for this workflow).

### GitHub Packages (Java)

CI publishes with `GITHUB_TOKEN` (`packages: write`). No extra Maven secrets.

Consumers add the repo + a PAT with `read:packages` — see [`java/README.md`](java/README.md).

```bash
DRY_RUN=1 CLIENTS=java FORCE=1 make release-clients
GITHUB_TOKEN=… CLIENTS=java FORCE=1 make release-client-java
```

### Manual / dry-run

```bash
DRY_RUN=1 make release-clients # detect changes since HEAD~1
DRY_RUN=1 CLIENTS=typescript FORCE=1 make release-clients
NODE_AUTH_TOKEN=… make release-client-typescript
PYPI_API_TOKEN=… make release-client-python
DRY_RUN=1 CLIENTS=java FORCE=1 make release-clients
CLIENTS=typescript FORCE=1 make release-clients
```
6 changes: 6 additions & 0 deletions clients/java/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
target/
.idea/
*.iml
.classpath
.project
.settings/
74 changes: 74 additions & 0 deletions clients/java/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# ai.afi:platform-client

Thin Java client for the AFI control plane (`/api/v1/platform`).

Distributed via **GitHub Packages**:
https://github.com/curefatih/afi/packages

```bash
cd clients/java && mvn test
```

### Install (Maven)

Add the GitHub Packages repository and dependency (needs a PAT with `read:packages`):

```xml
<repositories>
<repository>
<id>github</id>
<url>https://maven.pkg.github.com/curefatih/afi</url>
</repository>
</repositories>

<dependency>
<groupId>ai.afi</groupId>
<artifactId>platform-client</artifactId>
<version>1.0.0</version>
</dependency>
```

`~/.m2/settings.xml`:

```xml
<servers>
<server>
<id>github</id>
<username>YOUR_GITHUB_USERNAME</username>
<password>YOUR_PAT_WITH_READ_PACKAGES</password>
</server>
</servers>
```

```java
import ai.afi.platform.PlatformClient;

try (PlatformClient client = new PlatformClient("http://localhost:8081")) {
String token = client.login("admin@example.com", "secret").path("token").asText();
PlatformClient authed = new PlatformClient("http://localhost:8081", () -> token);
System.out.println(authed.listOrganizations());
}
```

Contract: [`../../api/openapi/platform.openapi.yaml`](../../api/openapi/platform.openapi.yaml).

## Gateway signed requests

```java
import ai.afi.platform.HttpSign;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;

byte[] pem = Files.readAllBytes(Path.of("signer.pem"));
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json");
headers.putAll(
HttpSign.signHeaders(
"POST",
"http://localhost:8080/v1/chat/completions",
body,
pem,
"local-signer"));
```
Loading