Skip to content

Commit 6f6bbfd

Browse files
committed
Add AI Gateway client and chat hook
1 parent 7ffdd9c commit 6f6bbfd

9 files changed

Lines changed: 1065 additions & 0 deletions

File tree

.changeset/warm-pans-learn.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@tailor-platform/app-shell": minor
3+
---
4+
5+
Add a low-level AI Gateway client and a simple text-only chat hook for AppShell.
6+
7+
```tsx
8+
import { createAIGatewayClient, useAIChat } from "@tailor-platform/app-shell";
9+
10+
const aiClient = createAIGatewayClient({ gatewayUri, authClient });
11+
const { messages, sendMessage, stop } = useAIChat({ client: aiClient, model: "gpt-5-mini" });
12+
```
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
---
2+
title: createAIGatewayClient
3+
description: Create a low-level AI Gateway client that reuses AppShell authentication
4+
---
5+
6+
# createAIGatewayClient
7+
8+
Creates a small AI Gateway transport client for text-only chat completions.
9+
10+
## Signature
11+
12+
```typescript
13+
function createAIGatewayClient(config: {
14+
gatewayUri: string;
15+
authClient: AuthClient;
16+
}): AIGatewayClient;
17+
```
18+
19+
## Parameters
20+
21+
### `gatewayUri`
22+
23+
- **Type:** `string`
24+
- **Required:** Yes
25+
- **Description:** Base URL of the AI Gateway
26+
27+
### `authClient`
28+
29+
- **Type:** `AuthClient`
30+
- **Required:** Yes
31+
- **Description:** Auth client used for authenticated requests via `authClient.fetch(...)`
32+
33+
## Return Value
34+
35+
```typescript
36+
interface AIGatewayClient {
37+
chatCompletionStream(request: AIGatewayChatRequest): AsyncGenerator<string, void, unknown>;
38+
}
39+
```
40+
41+
The generator yields text deltas. Concatenate them to build the assistant response.
42+
43+
## Related Types
44+
45+
```typescript
46+
interface AIGatewayChatMessage {
47+
role: "system" | "user" | "assistant";
48+
content: string;
49+
}
50+
51+
interface AIGatewayChatRequest {
52+
model: string;
53+
messages: AIGatewayChatMessage[];
54+
signal?: AbortSignal;
55+
}
56+
```
57+
58+
## Usage
59+
60+
```typescript
61+
import { createAuthClient, createAIGatewayClient } from "@tailor-platform/app-shell";
62+
63+
const authClient = createAuthClient({
64+
clientId: "your-client-id",
65+
appUri: "https://xyz.erp.dev",
66+
});
67+
68+
const aiClient = createAIGatewayClient({
69+
gatewayUri: "https://your-ai-gateway.example.com",
70+
authClient,
71+
});
72+
73+
const deltas: string[] = [];
74+
for await (const delta of aiClient.chatCompletionStream({
75+
model: "gpt-5-mini",
76+
messages: [{ role: "user", content: "Hello" }],
77+
})) {
78+
deltas.push(delta);
79+
}
80+
81+
console.log(deltas.join(""));
82+
```
83+
84+
## Notes
85+
86+
- Non-Gemini models are consumed as streaming SSE responses
87+
- `gemini-*` models are consumed as JSON responses and yielded once
88+
- The public API is intentionally text-only
89+
90+
## Related
91+
92+
- [Authentication Concept](../concepts/authentication.md)
93+
- [useAIChat](./use-ai-chat.md)

docs/api/use-ai-chat.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
---
2+
title: useAIChat
3+
description: Simple text-only chat hook for AI Gateway
4+
---
5+
6+
# useAIChat
7+
8+
React hook for simple text-only chat on top of `createAIGatewayClient`.
9+
10+
## Signature
11+
12+
```typescript
13+
const useAIChat: (config: { client: AIGatewayClient; model: string }) => {
14+
messages: AIChatMessage[];
15+
status: "ready" | "submitted" | "streaming" | "error";
16+
error?: Error;
17+
sendMessage: (message: { text: string } | string) => Promise<void>;
18+
stop: () => void;
19+
};
20+
```
21+
22+
## Return Value
23+
24+
### `messages`
25+
26+
```typescript
27+
interface AIChatMessage {
28+
id: string;
29+
role: "user" | "assistant";
30+
content: string;
31+
}
32+
```
33+
34+
### `status`
35+
36+
- **Type:** `"ready" | "submitted" | "streaming" | "error"`
37+
- **Description:** Current request state
38+
39+
### `error`
40+
41+
- **Type:** `Error | undefined`
42+
- **Description:** Last request error, if any
43+
44+
### `sendMessage()`
45+
46+
- **Type:** `(message: { text: string } | string) => Promise<void>`
47+
- **Description:** Appends a user message and streams the assistant response
48+
49+
### `stop()`
50+
51+
- **Type:** `() => void`
52+
- **Description:** Aborts the current request if one is in progress
53+
54+
## Usage
55+
56+
```tsx
57+
import { createAuthClient, createAIGatewayClient, useAIChat } from "@tailor-platform/app-shell";
58+
59+
const authClient = createAuthClient({
60+
clientId: "your-client-id",
61+
appUri: "https://xyz.erp.dev",
62+
});
63+
64+
const aiClient = createAIGatewayClient({
65+
gatewayUri: "https://your-ai-gateway.example.com",
66+
authClient,
67+
});
68+
69+
export function ChatScreen() {
70+
const { messages, sendMessage, status, stop, error } = useAIChat({
71+
client: aiClient,
72+
model: "gpt-5-mini",
73+
});
74+
75+
return (
76+
<div>
77+
{messages.map((message) => (
78+
<div key={message.id}>
79+
{message.role}: {message.content}
80+
</div>
81+
))}
82+
83+
<button
84+
onClick={() => sendMessage("Hello")}
85+
disabled={status === "submitted" || status === "streaming"}
86+
>
87+
Send
88+
</button>
89+
<button onClick={stop} disabled={status !== "submitted" && status !== "streaming"}>
90+
Stop
91+
</button>
92+
{error ? <div>{error.message}</div> : null}
93+
</div>
94+
);
95+
}
96+
```
97+
98+
## Notes
99+
100+
- The hook is intentionally text-only
101+
- System prompts and custom history shaping should use the low-level client directly
102+
- `stop()` keeps any already-streamed assistant text
103+
104+
## Related
105+
106+
- [createAIGatewayClient](./create-ai-gateway-client.md)

docs/concepts/authentication.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,46 @@ function App() {
154154
}
155155
```
156156

157+
### Using `createAIGatewayClient` with AI Gateway
158+
159+
`createAIGatewayClient` reuses the same authenticated fetch path as the rest of AppShell. Requests go through `authClient.fetch`, so DPoP proof generation and token refresh stay in the auth layer.
160+
161+
```tsx
162+
import { createAuthClient, createAIGatewayClient, useAIChat } from "@tailor-platform/app-shell";
163+
164+
const authClient = createAuthClient({
165+
clientId: "your-client-id",
166+
appUri: "https://xyz.erp.dev",
167+
});
168+
169+
const aiClient = createAIGatewayClient({
170+
gatewayUri: "https://your-ai-gateway.example.com",
171+
authClient,
172+
});
173+
174+
function ChatScreen() {
175+
const { messages, sendMessage, status, stop } = useAIChat({
176+
client: aiClient,
177+
model: "gpt-5-mini",
178+
});
179+
180+
return (
181+
<div>
182+
{messages.map((message) => (
183+
<div key={message.id}>
184+
{message.role}: {message.content}
185+
</div>
186+
))}
187+
188+
<button onClick={() => sendMessage("Hello")}>Send</button>
189+
<button onClick={stop} disabled={status !== "submitted" && status !== "streaming"}>
190+
Stop
191+
</button>
192+
</div>
193+
);
194+
}
195+
```
196+
157197
### `AuthClientConfig`
158198

159199
| Property | Type | Required | Description |

0 commit comments

Comments
 (0)