@flint-dev/sdk 0.2.0
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.
- package/README.md +288 -0
- package/dist/app-server-client.d.ts +84 -0
- package/dist/app-server-client.d.ts.map +1 -0
- package/dist/app-server-client.js +431 -0
- package/dist/create-client.d.ts +7 -0
- package/dist/create-client.d.ts.map +1 -0
- package/dist/create-client.js +10 -0
- package/dist/gateway-client.d.ts +76 -0
- package/dist/gateway-client.d.ts.map +1 -0
- package/dist/gateway-client.js +97 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/providers.d.ts +11 -0
- package/dist/providers.d.ts.map +1 -0
- package/dist/providers.js +64 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# @flint-dev/sdk
|
|
2
|
+
|
|
3
|
+
Client library for Flint app servers and the Flint gateway.
|
|
4
|
+
|
|
5
|
+
It includes:
|
|
6
|
+
|
|
7
|
+
- `AppServerClient` / `createClient(...)` for Codex-style app server protocol over stdio.
|
|
8
|
+
- `GatewayClient` for HTTP calls to the Flint gateway (`/v1/threads` APIs).
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
bun add @flint-dev/sdk
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Then install the app server for the provider you want to use:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
bun add @flint-dev/claude-app-server # Claude (Anthropic)
|
|
20
|
+
bun add @flint-dev/pi-app-server # Pi (multi-provider)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick start (app server)
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { createClient } from "@flint-dev/sdk";
|
|
27
|
+
|
|
28
|
+
const client = createClient({ provider: "claude", cwd: process.cwd() });
|
|
29
|
+
await client.start();
|
|
30
|
+
await client.createThread();
|
|
31
|
+
|
|
32
|
+
for await (const event of client.prompt("Explain this codebase")) {
|
|
33
|
+
switch (event.type) {
|
|
34
|
+
case "text":
|
|
35
|
+
process.stdout.write(event.delta);
|
|
36
|
+
break;
|
|
37
|
+
case "tool_start":
|
|
38
|
+
console.log(`\n[${event.name}]`);
|
|
39
|
+
break;
|
|
40
|
+
case "tool_end":
|
|
41
|
+
console.log(event.isError ? "[failed]" : "[done]");
|
|
42
|
+
break;
|
|
43
|
+
case "error":
|
|
44
|
+
console.error(event.message);
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
client.close();
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Quick start (gateway HTTP)
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { GatewayClient } from "@flint-dev/sdk";
|
|
56
|
+
|
|
57
|
+
const gateway = new GatewayClient({ baseUrl: "http://127.0.0.1:8788" });
|
|
58
|
+
|
|
59
|
+
const created = await gateway.createThread({
|
|
60
|
+
channel: "slack",
|
|
61
|
+
userId: "u1",
|
|
62
|
+
text: "Summarize the backlog",
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const followup = await gateway.sendThreadMessage(created.threadId, "Now draft next steps");
|
|
66
|
+
console.log(followup.reply);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Providers
|
|
70
|
+
|
|
71
|
+
A provider resolves to the command and arguments needed to spawn an app server. Three are built in:
|
|
72
|
+
|
|
73
|
+
| Name | App server | Default model |
|
|
74
|
+
| ---------- | ---------------------------------- | ------------------------- |
|
|
75
|
+
| `"claude"` | `@flint-dev/claude-app-server` | `claude-opus-4-6` |
|
|
76
|
+
| `"pi"` | `@flint-dev/pi-app-server` | `google/gemini-2.5-flash` |
|
|
77
|
+
| `"codex"` | `codex app-server` (system binary) | — |
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
// Use a built-in provider
|
|
81
|
+
const client = createClient({ provider: "pi", cwd: process.cwd() });
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Custom providers
|
|
85
|
+
|
|
86
|
+
Register your own provider to point at any Codex-protocol-compatible server:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { registerProvider, createClient } from "@flint-dev/sdk";
|
|
90
|
+
|
|
91
|
+
registerProvider("my-server", {
|
|
92
|
+
resolve(config) {
|
|
93
|
+
return {
|
|
94
|
+
command: "my-app-server",
|
|
95
|
+
args: ["--cwd", config.cwd],
|
|
96
|
+
cwd: config.cwd,
|
|
97
|
+
env: config.env,
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const client = createClient({ provider: "my-server", cwd: process.cwd() });
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Manual configuration
|
|
106
|
+
|
|
107
|
+
Skip providers entirely and pass spawn options directly:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { AppServerClient } from "@flint-dev/sdk";
|
|
111
|
+
|
|
112
|
+
const client = new AppServerClient({
|
|
113
|
+
command: "bun",
|
|
114
|
+
args: ["run", "./my-server/index.ts"],
|
|
115
|
+
cwd: process.cwd(),
|
|
116
|
+
env: { MY_API_KEY: "..." },
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## API
|
|
121
|
+
|
|
122
|
+
### `createClient(options): AppServerClient`
|
|
123
|
+
|
|
124
|
+
Factory that resolves a provider name to spawn configuration.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
type CreateClientOptions =
|
|
128
|
+
| { provider: string; cwd: string; env?: Record<string, string> }
|
|
129
|
+
| AppServerClientOptions;
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### `AppServerClient`
|
|
133
|
+
|
|
134
|
+
#### `new AppServerClient(options)`
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
interface AppServerClientOptions {
|
|
138
|
+
command: string;
|
|
139
|
+
args?: string[];
|
|
140
|
+
cwd: string;
|
|
141
|
+
env?: Record<string, string>;
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
#### `client.start(): Promise<void>`
|
|
146
|
+
|
|
147
|
+
Spawns the app server process and sends the `initialize` handshake.
|
|
148
|
+
|
|
149
|
+
#### `client.createThread(options?): Promise<string>`
|
|
150
|
+
|
|
151
|
+
Creates a new conversation thread. Returns the thread ID.
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
interface CreateThreadOptions {
|
|
155
|
+
model?: string;
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### `client.prompt(text, options?): AsyncGenerator<AgentEvent>`
|
|
160
|
+
|
|
161
|
+
Sends a prompt and yields events as they stream back. Automatically creates a thread if one hasn't been created yet.
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
interface PromptOptions {
|
|
165
|
+
model?: string;
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
#### `client.interrupt(): Promise<void>`
|
|
170
|
+
|
|
171
|
+
Interrupts the current turn.
|
|
172
|
+
|
|
173
|
+
#### `client.close(): void`
|
|
174
|
+
|
|
175
|
+
Kills the app server process.
|
|
176
|
+
|
|
177
|
+
### `GatewayClient`
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
new GatewayClient({
|
|
181
|
+
baseUrl: string,
|
|
182
|
+
headers?: HeadersInit,
|
|
183
|
+
fetch?: typeof fetch,
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
#### `gateway.health(): Promise<GatewayHealth>`
|
|
188
|
+
|
|
189
|
+
Calls `GET /v1/health`.
|
|
190
|
+
|
|
191
|
+
#### `gateway.listThreads(): Promise<GatewayThreadRecord[]>`
|
|
192
|
+
|
|
193
|
+
Calls `GET /v1/threads`.
|
|
194
|
+
|
|
195
|
+
#### `gateway.getThread(threadId): Promise<GatewayThreadRecord | undefined>`
|
|
196
|
+
|
|
197
|
+
Calls `GET /v1/threads/:threadId`. Returns `undefined` on 404.
|
|
198
|
+
|
|
199
|
+
#### `gateway.createThread(payload, idempotencyKey?): Promise<GatewayReply>`
|
|
200
|
+
|
|
201
|
+
Calls `POST /v1/threads`.
|
|
202
|
+
|
|
203
|
+
#### `gateway.sendThreadMessage(threadId, payloadOrText, idempotencyKey?): Promise<GatewayReply>`
|
|
204
|
+
|
|
205
|
+
Calls `POST /v1/threads/:threadId`.
|
|
206
|
+
|
|
207
|
+
#### `gateway.interruptThread(threadId): Promise<boolean>`
|
|
208
|
+
|
|
209
|
+
Calls `POST /v1/threads/:threadId/interrupt`. Returns `false` when gateway reports no active runtime (`409`).
|
|
210
|
+
|
|
211
|
+
#### `GatewayHttpError`
|
|
212
|
+
|
|
213
|
+
Thrown for non-2xx responses (except handled `404`/`409` cases above). Includes:
|
|
214
|
+
|
|
215
|
+
- `status`: HTTP status code
|
|
216
|
+
- `body`: parsed JSON response body when available
|
|
217
|
+
|
|
218
|
+
### `AgentEvent`
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
type AgentEvent =
|
|
222
|
+
| { type: "text"; delta: string }
|
|
223
|
+
| { type: "reasoning"; delta: string }
|
|
224
|
+
| { type: "tool_start"; id: string; name: string; input?: unknown }
|
|
225
|
+
| { type: "tool_end"; id: string; result?: unknown; isError: boolean }
|
|
226
|
+
| { type: "done"; usage?: { input: number; output: number } }
|
|
227
|
+
| { type: "error"; message: string };
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
| Event | Description |
|
|
231
|
+
| ------------ | ------------------------------------------------------------------ |
|
|
232
|
+
| `text` | Streamed text token from the agent |
|
|
233
|
+
| `reasoning` | Streamed reasoning/thinking token |
|
|
234
|
+
| `tool_start` | Agent began using a tool (`Bash`, `Edit`, `Write`, or an MCP tool) |
|
|
235
|
+
| `tool_end` | Tool execution finished |
|
|
236
|
+
| `done` | Turn completed |
|
|
237
|
+
| `error` | Turn failed |
|
|
238
|
+
|
|
239
|
+
## Local development
|
|
240
|
+
|
|
241
|
+
To use the SDK from a local checkout of the flint repo without publishing to npm:
|
|
242
|
+
|
|
243
|
+
### bun link
|
|
244
|
+
|
|
245
|
+
Symlinks packages globally, then into your project. Best for active development since changes are reflected immediately.
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
# Register the packages (run once from the flint repo)
|
|
249
|
+
cd packages/sdk && bun link
|
|
250
|
+
cd packages/app-server && bun link # if using Claude
|
|
251
|
+
cd packages/pi-app-server && bun link # if using Pi
|
|
252
|
+
|
|
253
|
+
# Link them into your project
|
|
254
|
+
cd ~/my-app
|
|
255
|
+
bun link @flint-dev/sdk
|
|
256
|
+
bun link @flint-dev/claude-app-server # or @flint-dev/pi-app-server
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### file: dependencies
|
|
260
|
+
|
|
261
|
+
Point at the local packages directly in your `package.json`:
|
|
262
|
+
|
|
263
|
+
```json
|
|
264
|
+
{
|
|
265
|
+
"dependencies": {
|
|
266
|
+
"@flint-dev/sdk": "file:../flint/packages/sdk",
|
|
267
|
+
"@flint-dev/claude-app-server": "file:../flint/packages/app-server"
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Then run `bun install`.
|
|
273
|
+
|
|
274
|
+
### bun pack
|
|
275
|
+
|
|
276
|
+
Creates a tarball, which is the closest to what `npm publish` actually produces. Useful for verifying the package is correctly configured before publishing.
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
cd packages/sdk && bun pack
|
|
280
|
+
# => flint-sdk-0.1.0.tgz
|
|
281
|
+
|
|
282
|
+
cd ~/my-app
|
|
283
|
+
bun add ../flint/packages/sdk/flint-sdk-0.1.0.tgz
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
## Requirements
|
|
287
|
+
|
|
288
|
+
- [Bun](https://bun.sh) runtime
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Server Client
|
|
3
|
+
*
|
|
4
|
+
* Spawns an app-server-compatible child process and communicates
|
|
5
|
+
* over stdio using JSON-RPC 2.0. Translates app-server notifications
|
|
6
|
+
* to AgentEvent for TUI consumption.
|
|
7
|
+
*/
|
|
8
|
+
import type { AgentEvent } from "./types";
|
|
9
|
+
export interface AppServerClientOptions {
|
|
10
|
+
/** Command to spawn the app server */
|
|
11
|
+
command: string;
|
|
12
|
+
/** Arguments to pass to the command */
|
|
13
|
+
args?: string[];
|
|
14
|
+
/** Working directory for the app server and Claude SDK */
|
|
15
|
+
cwd: string;
|
|
16
|
+
/** Environment variables for the app server process */
|
|
17
|
+
env?: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
export interface CreateThreadOptions {
|
|
20
|
+
/** Model to use for the thread (e.g. "claude-sonnet-4-5-20250929") */
|
|
21
|
+
model?: string;
|
|
22
|
+
/** Optional provider-specific system prompt override */
|
|
23
|
+
systemPrompt?: string;
|
|
24
|
+
/** Optional MCP server config (provider-specific) */
|
|
25
|
+
mcpServers?: Record<string, unknown>;
|
|
26
|
+
}
|
|
27
|
+
export interface PromptOptions {
|
|
28
|
+
/** Model override for this turn */
|
|
29
|
+
model?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface ResumeThreadOptions {
|
|
32
|
+
/** Override working directory when resuming */
|
|
33
|
+
cwd?: string;
|
|
34
|
+
/** Override model when resuming */
|
|
35
|
+
model?: string;
|
|
36
|
+
/** Optional MCP server config (provider-specific) */
|
|
37
|
+
mcpServers?: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
export declare class AppServerClient {
|
|
40
|
+
private stdin;
|
|
41
|
+
private stdout;
|
|
42
|
+
private stderr;
|
|
43
|
+
private proc;
|
|
44
|
+
private nextId;
|
|
45
|
+
private pendingRequests;
|
|
46
|
+
private notificationListeners;
|
|
47
|
+
private buffer;
|
|
48
|
+
private stderrBuffer;
|
|
49
|
+
private stderrHistory;
|
|
50
|
+
private isClosing;
|
|
51
|
+
private initialized;
|
|
52
|
+
private threadId;
|
|
53
|
+
private currentTurnId;
|
|
54
|
+
private readonly command;
|
|
55
|
+
private readonly args;
|
|
56
|
+
private readonly cwd;
|
|
57
|
+
private readonly env;
|
|
58
|
+
constructor(options: AppServerClientOptions);
|
|
59
|
+
/** Start the app server process and initialize it. */
|
|
60
|
+
start(): Promise<void>;
|
|
61
|
+
/** Create a new thread. Returns the thread ID. */
|
|
62
|
+
createThread(options?: CreateThreadOptions): Promise<string>;
|
|
63
|
+
/** Resume an existing thread. Returns the thread ID. */
|
|
64
|
+
resumeThread(threadId: string, options?: ResumeThreadOptions): Promise<string>;
|
|
65
|
+
/** Get the active thread ID (if any). */
|
|
66
|
+
getThreadId(): string | null;
|
|
67
|
+
/** Send a prompt and yield AgentEvents as they stream in. */
|
|
68
|
+
prompt(prompt: string, options?: PromptOptions): AsyncGenerator<AgentEvent>;
|
|
69
|
+
/** Interrupt the current turn. */
|
|
70
|
+
interrupt(): Promise<void>;
|
|
71
|
+
/** Stop the app server process. */
|
|
72
|
+
close(): void;
|
|
73
|
+
private request;
|
|
74
|
+
private notify;
|
|
75
|
+
private readStdout;
|
|
76
|
+
private readStderr;
|
|
77
|
+
private pushStderrLine;
|
|
78
|
+
private stderrTail;
|
|
79
|
+
private watchProcessExit;
|
|
80
|
+
private handleMessage;
|
|
81
|
+
/** Translate app-server JSON-RPC notifications to AgentEvents. */
|
|
82
|
+
private translateNotification;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=app-server-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-server-client.d.ts","sourceRoot":"","sources":["../src/app-server-client.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAqB1C,MAAM,WAAW,sBAAsB;IACrC,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,0DAA0D;IAC1D,GAAG,EAAE,MAAM,CAAC;IACZ,uDAAuD;IACvD,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,mBAAmB;IAClC,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,aAAa;IAC5B,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,MAAM,CAA2C;IACzD,OAAO,CAAC,MAAM,CAA2C;IACzD,OAAO,CAAC,IAAI,CAA6C;IACzD,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,eAAe,CAGnB;IACJ,OAAO,CAAC,qBAAqB,CAA0D;IACvF,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAW;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAqC;gBAE7C,OAAO,EAAE,sBAAsB;IAO3C,sDAAsD;IAChD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAgC5B,kDAAkD;IAC5C,YAAY,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;IAWlE,wDAAwD;IAClD,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;IAWpF,yCAAyC;IACzC,WAAW,IAAI,MAAM,GAAG,IAAI;IAI5B,6DAA6D;IACtD,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,cAAc,CAAC,UAAU,CAAC;IAuDlF,kCAAkC;IAC5B,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAYhC,mCAAmC;IACnC,KAAK,IAAI,IAAI;YAqBC,OAAO;IAoBrB,OAAO,CAAC,MAAM;YASA,UAAU;YA4BV,UAAU;IA8BxB,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,UAAU;YAIJ,gBAAgB;IAgB9B,OAAO,CAAC,aAAa;IA8BrB,kEAAkE;IAClE,OAAO,CAAC,qBAAqB;CA6H9B"}
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App Server Client
|
|
3
|
+
*
|
|
4
|
+
* Spawns an app-server-compatible child process and communicates
|
|
5
|
+
* over stdio using JSON-RPC 2.0. Translates app-server notifications
|
|
6
|
+
* to AgentEvent for TUI consumption.
|
|
7
|
+
*/
|
|
8
|
+
export class AppServerClient {
|
|
9
|
+
stdin = null;
|
|
10
|
+
stdout = null;
|
|
11
|
+
stderr = null;
|
|
12
|
+
proc = null;
|
|
13
|
+
nextId = 1;
|
|
14
|
+
pendingRequests = new Map();
|
|
15
|
+
notificationListeners = [];
|
|
16
|
+
buffer = "";
|
|
17
|
+
stderrBuffer = "";
|
|
18
|
+
stderrHistory = [];
|
|
19
|
+
isClosing = false;
|
|
20
|
+
initialized = false;
|
|
21
|
+
threadId = null;
|
|
22
|
+
currentTurnId = null;
|
|
23
|
+
command;
|
|
24
|
+
args;
|
|
25
|
+
cwd;
|
|
26
|
+
env;
|
|
27
|
+
constructor(options) {
|
|
28
|
+
this.command = options.command;
|
|
29
|
+
this.args = options.args ?? [];
|
|
30
|
+
this.cwd = options.cwd;
|
|
31
|
+
this.env = options.env;
|
|
32
|
+
}
|
|
33
|
+
/** Start the app server process and initialize it. */
|
|
34
|
+
async start() {
|
|
35
|
+
this.isClosing = false;
|
|
36
|
+
const proc = Bun.spawn([this.command, ...this.args], {
|
|
37
|
+
stdin: "pipe",
|
|
38
|
+
stdout: "pipe",
|
|
39
|
+
stderr: "pipe",
|
|
40
|
+
cwd: this.cwd,
|
|
41
|
+
...(this.env && { env: { ...process.env, ...this.env } }),
|
|
42
|
+
});
|
|
43
|
+
this.proc = proc;
|
|
44
|
+
this.stdin = proc.stdin;
|
|
45
|
+
this.stdout = proc.stdout;
|
|
46
|
+
this.stderr = proc.stderr;
|
|
47
|
+
// Read stdout in background
|
|
48
|
+
this.readStdout();
|
|
49
|
+
this.readStderr();
|
|
50
|
+
this.watchProcessExit(proc);
|
|
51
|
+
// Initialize the server
|
|
52
|
+
await this.request("initialize", {
|
|
53
|
+
clientInfo: {
|
|
54
|
+
name: "flint-tui",
|
|
55
|
+
version: "0.1.0",
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
// Send initialized notification (Codex protocol)
|
|
59
|
+
this.notify("initialized");
|
|
60
|
+
this.initialized = true;
|
|
61
|
+
}
|
|
62
|
+
/** Create a new thread. Returns the thread ID. */
|
|
63
|
+
async createThread(options) {
|
|
64
|
+
const result = (await this.request("thread/start", {
|
|
65
|
+
cwd: this.cwd,
|
|
66
|
+
...(options?.model && { model: options.model }),
|
|
67
|
+
...(options?.systemPrompt && { systemPrompt: options.systemPrompt }),
|
|
68
|
+
...(options?.mcpServers && { mcpServers: options.mcpServers }),
|
|
69
|
+
}));
|
|
70
|
+
this.threadId = result.thread.id;
|
|
71
|
+
return this.threadId;
|
|
72
|
+
}
|
|
73
|
+
/** Resume an existing thread. Returns the thread ID. */
|
|
74
|
+
async resumeThread(threadId, options) {
|
|
75
|
+
const result = (await this.request("thread/resume", {
|
|
76
|
+
threadId,
|
|
77
|
+
...(options?.cwd && { cwd: options.cwd }),
|
|
78
|
+
...(options?.model && { model: options.model }),
|
|
79
|
+
...(options?.mcpServers && { mcpServers: options.mcpServers }),
|
|
80
|
+
}));
|
|
81
|
+
this.threadId = result.thread.id;
|
|
82
|
+
return this.threadId;
|
|
83
|
+
}
|
|
84
|
+
/** Get the active thread ID (if any). */
|
|
85
|
+
getThreadId() {
|
|
86
|
+
return this.threadId;
|
|
87
|
+
}
|
|
88
|
+
/** Send a prompt and yield AgentEvents as they stream in. */
|
|
89
|
+
async *prompt(prompt, options) {
|
|
90
|
+
if (!this.threadId) {
|
|
91
|
+
await this.createThread();
|
|
92
|
+
}
|
|
93
|
+
// Set up notification listener before sending request
|
|
94
|
+
const events = [];
|
|
95
|
+
let resolve = null;
|
|
96
|
+
let done = false;
|
|
97
|
+
const listener = (notification) => {
|
|
98
|
+
const translated = this.translateNotification(notification);
|
|
99
|
+
for (const event of translated) {
|
|
100
|
+
events.push(event);
|
|
101
|
+
resolve?.();
|
|
102
|
+
}
|
|
103
|
+
// Check for terminal notifications
|
|
104
|
+
if (notification.method === "turn/completed") {
|
|
105
|
+
done = true;
|
|
106
|
+
resolve?.();
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
this.notificationListeners.push(listener);
|
|
110
|
+
try {
|
|
111
|
+
// Start the turn
|
|
112
|
+
await this.request("turn/start", {
|
|
113
|
+
threadId: this.threadId,
|
|
114
|
+
input: [{ type: "text", text: prompt }],
|
|
115
|
+
...(options?.model && { model: options.model }),
|
|
116
|
+
});
|
|
117
|
+
// Yield events as they come in
|
|
118
|
+
while (!done) {
|
|
119
|
+
if (events.length === 0) {
|
|
120
|
+
await new Promise((r) => {
|
|
121
|
+
resolve = r;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
while (events.length > 0) {
|
|
125
|
+
yield events.shift();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Drain remaining
|
|
129
|
+
while (events.length > 0) {
|
|
130
|
+
yield events.shift();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
const idx = this.notificationListeners.indexOf(listener);
|
|
135
|
+
if (idx !== -1)
|
|
136
|
+
this.notificationListeners.splice(idx, 1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** Interrupt the current turn. */
|
|
140
|
+
async interrupt() {
|
|
141
|
+
if (!this.threadId || !this.currentTurnId)
|
|
142
|
+
return;
|
|
143
|
+
try {
|
|
144
|
+
await this.request("turn/interrupt", {
|
|
145
|
+
threadId: this.threadId,
|
|
146
|
+
turnId: this.currentTurnId,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// May fail if no turn in progress
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** Stop the app server process. */
|
|
154
|
+
close() {
|
|
155
|
+
this.isClosing = true;
|
|
156
|
+
if (this.proc) {
|
|
157
|
+
try {
|
|
158
|
+
this.stdin?.end();
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// Already closed
|
|
162
|
+
}
|
|
163
|
+
this.proc.kill();
|
|
164
|
+
this.proc = null;
|
|
165
|
+
this.stdin = null;
|
|
166
|
+
this.stdout = null;
|
|
167
|
+
this.stderr = null;
|
|
168
|
+
}
|
|
169
|
+
this.initialized = false;
|
|
170
|
+
this.threadId = null;
|
|
171
|
+
this.currentTurnId = null;
|
|
172
|
+
}
|
|
173
|
+
// ── Private ──────────────────────────────────────────────────────────────
|
|
174
|
+
async request(method, params) {
|
|
175
|
+
if (!this.stdin)
|
|
176
|
+
throw new Error("App server not started");
|
|
177
|
+
const id = this.nextId++;
|
|
178
|
+
const request = {
|
|
179
|
+
id,
|
|
180
|
+
method,
|
|
181
|
+
...(params !== undefined && { params }),
|
|
182
|
+
};
|
|
183
|
+
if (process.env.DEBUG) {
|
|
184
|
+
console.error(`[sdk] → ${method}`, params ? JSON.stringify(params) : "");
|
|
185
|
+
}
|
|
186
|
+
return new Promise((resolve, reject) => {
|
|
187
|
+
this.pendingRequests.set(id, { resolve, reject });
|
|
188
|
+
this.stdin.write(JSON.stringify(request) + "\n");
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
notify(method, params) {
|
|
192
|
+
if (!this.stdin)
|
|
193
|
+
throw new Error("App server not started");
|
|
194
|
+
const notification = {
|
|
195
|
+
method,
|
|
196
|
+
...(params !== undefined && { params }),
|
|
197
|
+
};
|
|
198
|
+
this.stdin.write(JSON.stringify(notification) + "\n");
|
|
199
|
+
}
|
|
200
|
+
async readStdout() {
|
|
201
|
+
if (!this.stdout)
|
|
202
|
+
return;
|
|
203
|
+
const reader = this.stdout.getReader();
|
|
204
|
+
const decoder = new TextDecoder();
|
|
205
|
+
try {
|
|
206
|
+
while (true) {
|
|
207
|
+
const { done, value } = await reader.read();
|
|
208
|
+
if (done)
|
|
209
|
+
break;
|
|
210
|
+
this.buffer += decoder.decode(value, { stream: true });
|
|
211
|
+
let newlineIdx;
|
|
212
|
+
while ((newlineIdx = this.buffer.indexOf("\n")) !== -1) {
|
|
213
|
+
const line = this.buffer.slice(0, newlineIdx);
|
|
214
|
+
this.buffer = this.buffer.slice(newlineIdx + 1);
|
|
215
|
+
if (line.trim()) {
|
|
216
|
+
this.handleMessage(line);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// Process exited
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async readStderr() {
|
|
226
|
+
if (!this.stderr)
|
|
227
|
+
return;
|
|
228
|
+
const reader = this.stderr.getReader();
|
|
229
|
+
const decoder = new TextDecoder();
|
|
230
|
+
try {
|
|
231
|
+
while (true) {
|
|
232
|
+
const { done, value } = await reader.read();
|
|
233
|
+
if (done)
|
|
234
|
+
break;
|
|
235
|
+
this.stderrBuffer += decoder.decode(value, { stream: true });
|
|
236
|
+
let newlineIdx;
|
|
237
|
+
while ((newlineIdx = this.stderrBuffer.indexOf("\n")) !== -1) {
|
|
238
|
+
const line = this.stderrBuffer.slice(0, newlineIdx);
|
|
239
|
+
this.stderrBuffer = this.stderrBuffer.slice(newlineIdx + 1);
|
|
240
|
+
this.pushStderrLine(line);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
// Process exited
|
|
246
|
+
}
|
|
247
|
+
if (this.stderrBuffer.trim()) {
|
|
248
|
+
this.pushStderrLine(this.stderrBuffer);
|
|
249
|
+
this.stderrBuffer = "";
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
pushStderrLine(line) {
|
|
253
|
+
const trimmed = line.trim();
|
|
254
|
+
if (!trimmed)
|
|
255
|
+
return;
|
|
256
|
+
this.stderrHistory.push(trimmed);
|
|
257
|
+
if (this.stderrHistory.length > 60) {
|
|
258
|
+
this.stderrHistory.shift();
|
|
259
|
+
}
|
|
260
|
+
if (process.env.DEBUG) {
|
|
261
|
+
console.error(`[sdk][${this.command} stderr] ${trimmed}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
stderrTail(maxLines = 8) {
|
|
265
|
+
return this.stderrHistory.slice(-maxLines).join("\n");
|
|
266
|
+
}
|
|
267
|
+
async watchProcessExit(proc) {
|
|
268
|
+
const exitCode = await proc.exited;
|
|
269
|
+
if (this.proc !== proc || this.isClosing)
|
|
270
|
+
return;
|
|
271
|
+
const tail = this.stderrTail();
|
|
272
|
+
const message = tail
|
|
273
|
+
? `App server exited with code ${exitCode}\nRecent stderr:\n${tail}`
|
|
274
|
+
: `App server exited with code ${exitCode}`;
|
|
275
|
+
const error = new Error(message);
|
|
276
|
+
for (const [, pending] of this.pendingRequests) {
|
|
277
|
+
pending.reject(error);
|
|
278
|
+
}
|
|
279
|
+
this.pendingRequests.clear();
|
|
280
|
+
}
|
|
281
|
+
handleMessage(line) {
|
|
282
|
+
let msg;
|
|
283
|
+
try {
|
|
284
|
+
msg = JSON.parse(line);
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
// Response (has id)
|
|
290
|
+
if ("id" in msg && msg.id != null) {
|
|
291
|
+
const response = msg;
|
|
292
|
+
const pending = this.pendingRequests.get(response.id);
|
|
293
|
+
if (pending) {
|
|
294
|
+
this.pendingRequests.delete(response.id);
|
|
295
|
+
if (response.error) {
|
|
296
|
+
pending.reject(new Error(response.error.message));
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
pending.resolve(response.result);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
// Notification (no id)
|
|
305
|
+
const notification = msg;
|
|
306
|
+
for (const listener of this.notificationListeners) {
|
|
307
|
+
listener(notification);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/** Translate app-server JSON-RPC notifications to AgentEvents. */
|
|
311
|
+
translateNotification(notification) {
|
|
312
|
+
const events = [];
|
|
313
|
+
const params = (notification.params ?? {});
|
|
314
|
+
switch (notification.method) {
|
|
315
|
+
case "item/agentMessage/delta": {
|
|
316
|
+
events.push({ type: "text", delta: params.delta });
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
case "item/reasoning/textDelta": {
|
|
320
|
+
events.push({ type: "reasoning", delta: params.delta });
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
case "item/started": {
|
|
324
|
+
const item = params.item;
|
|
325
|
+
if (!item)
|
|
326
|
+
break;
|
|
327
|
+
const itemType = item.type;
|
|
328
|
+
const itemId = item.id;
|
|
329
|
+
if (itemType === "commandExecution") {
|
|
330
|
+
events.push({
|
|
331
|
+
type: "tool_start",
|
|
332
|
+
id: itemId,
|
|
333
|
+
name: "Bash",
|
|
334
|
+
input: { command: item.command, cwd: item.cwd },
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
else if (itemType === "fileChange") {
|
|
338
|
+
const changes = item.changes;
|
|
339
|
+
const firstChange = changes?.[0];
|
|
340
|
+
const kindType = firstChange?.kind?.type;
|
|
341
|
+
const toolName = kindType === "add" ? "Write" : "Edit";
|
|
342
|
+
events.push({
|
|
343
|
+
type: "tool_start",
|
|
344
|
+
id: itemId,
|
|
345
|
+
name: toolName,
|
|
346
|
+
input: { file_path: firstChange?.path },
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
else if (itemType === "mcpToolCall") {
|
|
350
|
+
events.push({
|
|
351
|
+
type: "tool_start",
|
|
352
|
+
id: itemId,
|
|
353
|
+
name: String(item.tool ?? "tool"),
|
|
354
|
+
input: item.arguments,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
case "item/completed": {
|
|
360
|
+
const item = params.item;
|
|
361
|
+
if (!item)
|
|
362
|
+
break;
|
|
363
|
+
const itemId = item.id;
|
|
364
|
+
const itemType = item.type;
|
|
365
|
+
if (itemType === "commandExecution") {
|
|
366
|
+
const exitCode = item.exitCode;
|
|
367
|
+
events.push({
|
|
368
|
+
type: "tool_end",
|
|
369
|
+
id: itemId,
|
|
370
|
+
result: item.aggregatedOutput,
|
|
371
|
+
isError: (exitCode ?? 0) !== 0,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
else if (itemType === "fileChange") {
|
|
375
|
+
events.push({
|
|
376
|
+
type: "tool_end",
|
|
377
|
+
id: itemId,
|
|
378
|
+
result: undefined,
|
|
379
|
+
isError: false,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
else if (itemType === "mcpToolCall") {
|
|
383
|
+
events.push({
|
|
384
|
+
type: "tool_end",
|
|
385
|
+
id: itemId,
|
|
386
|
+
result: item.result,
|
|
387
|
+
isError: false,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
break;
|
|
391
|
+
}
|
|
392
|
+
case "turn/started": {
|
|
393
|
+
const turn = params.turn;
|
|
394
|
+
if (turn) {
|
|
395
|
+
this.currentTurnId = turn.id;
|
|
396
|
+
}
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
case "turn/completed": {
|
|
400
|
+
const turn = params.turn;
|
|
401
|
+
if (turn) {
|
|
402
|
+
const status = turn.status;
|
|
403
|
+
if (status === "failed") {
|
|
404
|
+
const error = turn.error;
|
|
405
|
+
events.push({
|
|
406
|
+
type: "error",
|
|
407
|
+
message: error?.message ?? "Unknown error",
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
events.push({ type: "done" });
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
events.push({ type: "done" });
|
|
416
|
+
}
|
|
417
|
+
this.currentTurnId = null;
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
case "error": {
|
|
421
|
+
// Separate error notification — already handled via turn/completed
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
// Ignored delta notifications that we don't translate to AgentEvent
|
|
425
|
+
case "item/commandExecution/outputDelta":
|
|
426
|
+
case "item/fileChange/outputDelta":
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
return events;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { AppServerClient, type AppServerClientOptions } from "./app-server-client";
|
|
2
|
+
import { type ProviderConfig } from "./providers";
|
|
3
|
+
export type CreateClientOptions = ({
|
|
4
|
+
provider: string;
|
|
5
|
+
} & ProviderConfig) | AppServerClientOptions;
|
|
6
|
+
export declare function createClient(options: CreateClientOptions): AppServerClient;
|
|
7
|
+
//# sourceMappingURL=create-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-client.d.ts","sourceRoot":"","sources":["../src/create-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AACnF,OAAO,EAAe,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAE/D,MAAM,MAAM,mBAAmB,GAAG,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,cAAc,CAAC,GAAG,sBAAsB,CAAC;AAEnG,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,eAAe,CAO1E"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AppServerClient } from "./app-server-client";
|
|
2
|
+
import { getProvider } from "./providers";
|
|
3
|
+
export function createClient(options) {
|
|
4
|
+
if ("provider" in options) {
|
|
5
|
+
const { provider, ...config } = options;
|
|
6
|
+
const resolved = getProvider(provider).resolve(config);
|
|
7
|
+
return new AppServerClient(resolved);
|
|
8
|
+
}
|
|
9
|
+
return new AppServerClient(options);
|
|
10
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export type GatewayRoutingMode = "main" | "per-peer" | "per-channel-peer" | "per-account-channel-peer";
|
|
2
|
+
export type GatewayChatType = "direct" | "group" | "channel";
|
|
3
|
+
export interface GatewayHealth {
|
|
4
|
+
ok: boolean;
|
|
5
|
+
provider: string;
|
|
6
|
+
defaultRoutingMode: GatewayRoutingMode;
|
|
7
|
+
}
|
|
8
|
+
export interface GatewayThreadRecord {
|
|
9
|
+
threadId: string;
|
|
10
|
+
routingMode: GatewayRoutingMode;
|
|
11
|
+
provider: string;
|
|
12
|
+
mcpProfileIds?: string[];
|
|
13
|
+
channel: string;
|
|
14
|
+
userId: string;
|
|
15
|
+
chatType: GatewayChatType;
|
|
16
|
+
peerId: string;
|
|
17
|
+
accountId?: string;
|
|
18
|
+
identityId?: string;
|
|
19
|
+
channelThreadId?: string;
|
|
20
|
+
createdAt: string;
|
|
21
|
+
updatedAt: string;
|
|
22
|
+
}
|
|
23
|
+
export interface GatewayCreateThreadRequest {
|
|
24
|
+
channel: string;
|
|
25
|
+
userId: string;
|
|
26
|
+
text: string;
|
|
27
|
+
mcpProfileIds?: string[];
|
|
28
|
+
provider?: string;
|
|
29
|
+
chatType?: GatewayChatType;
|
|
30
|
+
peerId?: string;
|
|
31
|
+
accountId?: string;
|
|
32
|
+
identityId?: string;
|
|
33
|
+
channelThreadId?: string;
|
|
34
|
+
routingMode?: GatewayRoutingMode;
|
|
35
|
+
idempotencyKey?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface GatewaySendThreadRequest {
|
|
38
|
+
text: string;
|
|
39
|
+
idempotencyKey?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface GatewayReply {
|
|
42
|
+
threadId: string;
|
|
43
|
+
routingMode: GatewayRoutingMode;
|
|
44
|
+
provider: string;
|
|
45
|
+
reply: string;
|
|
46
|
+
durationMs?: number;
|
|
47
|
+
idempotencyKey?: string;
|
|
48
|
+
cached?: boolean;
|
|
49
|
+
}
|
|
50
|
+
export interface GatewayClientOptions {
|
|
51
|
+
baseUrl: string;
|
|
52
|
+
headers?: ConstructorParameters<typeof Headers>[0];
|
|
53
|
+
fetch?: typeof fetch;
|
|
54
|
+
}
|
|
55
|
+
export declare class GatewayHttpError extends Error {
|
|
56
|
+
readonly status: number;
|
|
57
|
+
readonly body: unknown;
|
|
58
|
+
constructor(status: number, body: unknown);
|
|
59
|
+
}
|
|
60
|
+
export declare class GatewayClient {
|
|
61
|
+
private readonly baseUrl;
|
|
62
|
+
private readonly defaultHeaders;
|
|
63
|
+
private readonly fetchImpl;
|
|
64
|
+
constructor(options: GatewayClientOptions);
|
|
65
|
+
health(): Promise<GatewayHealth>;
|
|
66
|
+
listThreads(): Promise<GatewayThreadRecord[]>;
|
|
67
|
+
getThread(threadId: string): Promise<GatewayThreadRecord | undefined>;
|
|
68
|
+
createThread(payload: GatewayCreateThreadRequest, idempotencyKey?: string): Promise<GatewayReply>;
|
|
69
|
+
sendThreadMessage(threadId: string, payload: GatewaySendThreadRequest | string, idempotencyKey?: string): Promise<GatewayReply>;
|
|
70
|
+
interruptThread(threadId: string): Promise<boolean>;
|
|
71
|
+
private unwrapData;
|
|
72
|
+
private requestJson;
|
|
73
|
+
private parseJsonOrThrow;
|
|
74
|
+
private request;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=gateway-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway-client.d.ts","sourceRoot":"","sources":["../src/gateway-client.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAC1B,MAAM,GACN,UAAU,GACV,kBAAkB,GAClB,0BAA0B,CAAC;AAE/B,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAE7D,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,kBAAkB,CAAC;CACxC;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,kBAAkB,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,eAAe,CAAC;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,kBAAkB,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,qBAAqB,CAAC,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACnD,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;gBAEX,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO;CAU1C;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAU;IACzC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;gBAE7B,OAAO,EAAE,oBAAoB;IASnC,MAAM,IAAI,OAAO,CAAC,aAAa,CAAC;IAIhC,WAAW,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAK7C,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAQrE,YAAY,CAChB,OAAO,EAAE,0BAA0B,EACnC,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,YAAY,CAAC;IAIlB,iBAAiB,CACrB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,wBAAwB,GAAG,MAAM,EAC1C,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC,YAAY,CAAC;IAUlB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;YAS3C,UAAU;YAQV,WAAW;YAUX,gBAAgB;IAgB9B,OAAO,CAAC,OAAO;CAuBhB"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export class GatewayHttpError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
body;
|
|
4
|
+
constructor(status, body) {
|
|
5
|
+
const errorMessage = body && typeof body === "object" && "error" in body && typeof body.error === "string"
|
|
6
|
+
? body.error
|
|
7
|
+
: `Gateway request failed with status ${status}`;
|
|
8
|
+
super(errorMessage);
|
|
9
|
+
this.name = "GatewayHttpError";
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.body = body;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class GatewayClient {
|
|
15
|
+
baseUrl;
|
|
16
|
+
defaultHeaders;
|
|
17
|
+
fetchImpl;
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.baseUrl = options.baseUrl.trim().replace(/\/+$/, "");
|
|
20
|
+
if (!this.baseUrl) {
|
|
21
|
+
throw new Error("GatewayClient requires a non-empty baseUrl.");
|
|
22
|
+
}
|
|
23
|
+
this.defaultHeaders = new Headers(options.headers);
|
|
24
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
25
|
+
}
|
|
26
|
+
async health() {
|
|
27
|
+
return this.requestJson("GET", "/v1/health");
|
|
28
|
+
}
|
|
29
|
+
async listThreads() {
|
|
30
|
+
const result = await this.requestJson("GET", "/v1/threads");
|
|
31
|
+
return result.data;
|
|
32
|
+
}
|
|
33
|
+
async getThread(threadId) {
|
|
34
|
+
const response = await this.request("GET", `/v1/threads/${encodeURIComponent(threadId)}`);
|
|
35
|
+
if (response.status === 404) {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
return this.unwrapData(response);
|
|
39
|
+
}
|
|
40
|
+
async createThread(payload, idempotencyKey) {
|
|
41
|
+
return this.requestJson("POST", "/v1/threads", payload, idempotencyKey);
|
|
42
|
+
}
|
|
43
|
+
async sendThreadMessage(threadId, payload, idempotencyKey) {
|
|
44
|
+
const body = typeof payload === "string" ? { text: payload } : payload;
|
|
45
|
+
return this.requestJson("POST", `/v1/threads/${encodeURIComponent(threadId)}`, body, idempotencyKey);
|
|
46
|
+
}
|
|
47
|
+
async interruptThread(threadId) {
|
|
48
|
+
const response = await this.request("POST", `/v1/threads/${encodeURIComponent(threadId)}/interrupt`);
|
|
49
|
+
if (response.status === 409) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
const result = await this.parseJsonOrThrow(response);
|
|
53
|
+
return result.interrupted;
|
|
54
|
+
}
|
|
55
|
+
async unwrapData(response) {
|
|
56
|
+
const parsed = await this.parseJsonOrThrow(response);
|
|
57
|
+
if (!parsed || typeof parsed !== "object" || !("data" in parsed)) {
|
|
58
|
+
throw new Error("Gateway response is missing data.");
|
|
59
|
+
}
|
|
60
|
+
return parsed.data;
|
|
61
|
+
}
|
|
62
|
+
async requestJson(method, path, body, idempotencyKey) {
|
|
63
|
+
const response = await this.request(method, path, body, idempotencyKey);
|
|
64
|
+
return this.parseJsonOrThrow(response);
|
|
65
|
+
}
|
|
66
|
+
async parseJsonOrThrow(response) {
|
|
67
|
+
const text = await response.text();
|
|
68
|
+
let parsed;
|
|
69
|
+
if (text.length > 0) {
|
|
70
|
+
try {
|
|
71
|
+
parsed = JSON.parse(text);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
parsed = undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
throw new GatewayHttpError(response.status, parsed);
|
|
79
|
+
}
|
|
80
|
+
return parsed;
|
|
81
|
+
}
|
|
82
|
+
request(method, path, body, idempotencyKey) {
|
|
83
|
+
const headers = new Headers(this.defaultHeaders);
|
|
84
|
+
if (idempotencyKey?.trim()) {
|
|
85
|
+
headers.set("idempotency-key", idempotencyKey.trim());
|
|
86
|
+
}
|
|
87
|
+
const requestInit = {
|
|
88
|
+
method,
|
|
89
|
+
headers,
|
|
90
|
+
};
|
|
91
|
+
if (body !== undefined) {
|
|
92
|
+
headers.set("content-type", "application/json");
|
|
93
|
+
requestInit.body = JSON.stringify(body);
|
|
94
|
+
}
|
|
95
|
+
return this.fetchImpl(`${this.baseUrl}${path}`, requestInit);
|
|
96
|
+
}
|
|
97
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { AppServerClient, type AppServerClientOptions, type CreateThreadOptions, type PromptOptions, type ResumeThreadOptions, } from "./app-server-client";
|
|
2
|
+
export type { AgentEvent } from "./types";
|
|
3
|
+
export { createClient, type CreateClientOptions } from "./create-client";
|
|
4
|
+
export { registerProvider, type Provider, type ProviderConfig } from "./providers";
|
|
5
|
+
export { GatewayClient, GatewayHttpError, type GatewayChatType, type GatewayClientOptions, type GatewayCreateThreadRequest, type GatewayHealth, type GatewayReply, type GatewayRoutingMode, type GatewaySendThreadRequest, type GatewayThreadRecord, } from "./gateway-client";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,EACxB,KAAK,aAAa,EAClB,KAAK,mBAAmB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AACnF,OAAO,EACL,aAAa,EACb,gBAAgB,EAChB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,KAAK,0BAA0B,EAC/B,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,GACzB,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AppServerClientOptions } from "./app-server-client";
|
|
2
|
+
export interface ProviderConfig {
|
|
3
|
+
cwd: string;
|
|
4
|
+
env?: Record<string, string>;
|
|
5
|
+
}
|
|
6
|
+
export interface Provider {
|
|
7
|
+
resolve(config: ProviderConfig): AppServerClientOptions;
|
|
8
|
+
}
|
|
9
|
+
export declare function registerProvider(name: string, provider: Provider): void;
|
|
10
|
+
export declare function getProvider(name: string): Provider;
|
|
11
|
+
//# sourceMappingURL=providers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../src/providers.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAElE,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,CAAC,MAAM,EAAE,cAAc,GAAG,sBAAsB,CAAC;CACzD;AA+CD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAEvE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAMlD"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { join, dirname } from "path";
|
|
2
|
+
import { createRequire } from "module";
|
|
3
|
+
const claudeProvider = {
|
|
4
|
+
resolve(config) {
|
|
5
|
+
const pkgPath = resolvePackagePath("@flint-dev/claude-app-server/package.json");
|
|
6
|
+
const entry = join(dirname(pkgPath), "src/index.ts");
|
|
7
|
+
return {
|
|
8
|
+
command: "bun",
|
|
9
|
+
args: ["run", entry],
|
|
10
|
+
cwd: config.cwd,
|
|
11
|
+
env: config.env,
|
|
12
|
+
};
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
const codexProvider = {
|
|
16
|
+
resolve(config) {
|
|
17
|
+
return {
|
|
18
|
+
command: "codex",
|
|
19
|
+
args: ["app-server"],
|
|
20
|
+
cwd: config.cwd,
|
|
21
|
+
env: config.env,
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
const piProvider = {
|
|
26
|
+
resolve(config) {
|
|
27
|
+
const pkgPath = resolvePackagePath("@flint-dev/pi-app-server/package.json");
|
|
28
|
+
const entry = join(dirname(pkgPath), "src/index.ts");
|
|
29
|
+
return {
|
|
30
|
+
command: "bun",
|
|
31
|
+
args: ["run", entry],
|
|
32
|
+
cwd: config.cwd,
|
|
33
|
+
env: config.env,
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
const builtins = new Map([
|
|
38
|
+
["claude", claudeProvider],
|
|
39
|
+
["codex", codexProvider],
|
|
40
|
+
["pi", piProvider],
|
|
41
|
+
]);
|
|
42
|
+
const custom = new Map();
|
|
43
|
+
export function registerProvider(name, provider) {
|
|
44
|
+
custom.set(name, provider);
|
|
45
|
+
}
|
|
46
|
+
export function getProvider(name) {
|
|
47
|
+
const provider = custom.get(name) ?? builtins.get(name);
|
|
48
|
+
if (!provider) {
|
|
49
|
+
throw new Error(`Unknown provider: "${name}". Register it with registerProvider().`);
|
|
50
|
+
}
|
|
51
|
+
return provider;
|
|
52
|
+
}
|
|
53
|
+
function resolvePackagePath(specifier) {
|
|
54
|
+
const consumerRequire = createRequire(join(process.cwd(), "__placeholder__.js"));
|
|
55
|
+
try {
|
|
56
|
+
// First try consumer resolution (published package use-case).
|
|
57
|
+
return consumerRequire.resolve(specifier);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// Fall back to SDK-local resolution (workspace/dev use-case).
|
|
61
|
+
const sdkRequire = createRequire(import.meta.url);
|
|
62
|
+
return sdkRequire.resolve(specifier);
|
|
63
|
+
}
|
|
64
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type AgentEvent = {
|
|
2
|
+
type: "init";
|
|
3
|
+
sessionId: string;
|
|
4
|
+
} | {
|
|
5
|
+
type: "text";
|
|
6
|
+
delta: string;
|
|
7
|
+
} | {
|
|
8
|
+
type: "reasoning";
|
|
9
|
+
delta: string;
|
|
10
|
+
} | {
|
|
11
|
+
type: "tool_start";
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
input?: unknown;
|
|
15
|
+
parentId?: string | null;
|
|
16
|
+
} | {
|
|
17
|
+
type: "tool_end";
|
|
18
|
+
id: string;
|
|
19
|
+
result?: unknown;
|
|
20
|
+
isError: boolean;
|
|
21
|
+
parentId?: string | null;
|
|
22
|
+
} | {
|
|
23
|
+
type: "done";
|
|
24
|
+
usage?: {
|
|
25
|
+
input: number;
|
|
26
|
+
output: number;
|
|
27
|
+
};
|
|
28
|
+
} | {
|
|
29
|
+
type: "error";
|
|
30
|
+
message: string;
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC3F;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC9F;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,GAC3D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flint-dev/sdk",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "TypeScript client SDK for Flint app servers",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Aaron Escalona",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"type": "module",
|
|
11
|
+
"main": "dist/index.js",
|
|
12
|
+
"types": "dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"prepack": "bun run build",
|
|
25
|
+
"typecheck": "tsc --noEmit"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"@flint-dev/claude-app-server": "^0.2.0",
|
|
30
|
+
"@flint-dev/pi-app-server": "^0.2.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependenciesMeta": {
|
|
33
|
+
"@flint-dev/claude-app-server": {
|
|
34
|
+
"optional": true
|
|
35
|
+
},
|
|
36
|
+
"@flint-dev/pi-app-server": {
|
|
37
|
+
"optional": true
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|