@opengeni/sdk 0.1.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 +157 -0
- package/dist/index.d.ts +1196 -0
- package/dist/index.js +955 -0
- package/dist/index.js.map +1 -0
- package/package.json +38 -0
- package/src/client.ts +846 -0
- package/src/errors.ts +40 -0
- package/src/index.ts +142 -0
- package/src/proxy.ts +171 -0
- package/src/sse.ts +90 -0
- package/src/stream.ts +192 -0
- package/src/types.ts +1036 -0
package/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# @opengeni/sdk
|
|
2
|
+
|
|
3
|
+
Framework-agnostic TypeScript SDK for the OpenGeni public API: a typed client,
|
|
4
|
+
session lifecycle, and the streaming core — SSE event streaming with automatic
|
|
5
|
+
reconnect, resume-by-sequence, gap backfill, and duplicate suppression — plus
|
|
6
|
+
helpers for proxying the stream through your own API.
|
|
7
|
+
|
|
8
|
+
Zero runtime dependencies. Needs only WHATWG `fetch` and streams, so it runs in
|
|
9
|
+
Node 18+, Bun, Deno, browsers, and edge runtimes.
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { OpenGeniClient } from "@opengeni/sdk";
|
|
15
|
+
|
|
16
|
+
const client = new OpenGeniClient({
|
|
17
|
+
baseUrl: "https://api.example.com",
|
|
18
|
+
apiKey: process.env.OPENGENI_API_KEY!,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const session = await client.createSession(workspaceId, {
|
|
22
|
+
initialMessage: "Investigate the failing deploy on staging",
|
|
23
|
+
resources: [{ kind: "repository", uri: "https://github.com/acme/app.git", ref: "main" }],
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
for await (const event of client.streamEvents(workspaceId, session.id)) {
|
|
27
|
+
if (event.type === "agent.message.delta") {
|
|
28
|
+
process.stdout.write((event.payload as { text: string }).text);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Streaming guarantees
|
|
34
|
+
|
|
35
|
+
`client.streamEvents(...)` (and the underlying `streamSessionEvents`) delivers
|
|
36
|
+
each session event **exactly once, in order**, anchored on the per-session
|
|
37
|
+
contiguous `sequence` number:
|
|
38
|
+
|
|
39
|
+
- Reconnects transparently on transient drops (network failures, 5xx, 429),
|
|
40
|
+
resuming from the last seen sequence via `?after=`.
|
|
41
|
+
- Suppresses duplicates when server replay overlaps what was already seen.
|
|
42
|
+
- Backfills any gap observed on a live connection from the durable replay
|
|
43
|
+
endpoint (`GET .../events?after=`) before yielding newer events.
|
|
44
|
+
- Ends gracefully when the provided `AbortSignal` aborts; throws on
|
|
45
|
+
non-retryable failures (e.g. 401/403/404).
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
const controller = new AbortController();
|
|
49
|
+
for await (const event of client.streamEvents(workspaceId, sessionId, {
|
|
50
|
+
after: lastSeenSequence,
|
|
51
|
+
signal: controller.signal,
|
|
52
|
+
onStateChange: (state) => console.log("stream:", state),
|
|
53
|
+
})) {
|
|
54
|
+
// ...
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Messages, the turn queue, and steering
|
|
59
|
+
|
|
60
|
+
Messages sent while a turn is running **queue by default** — visible,
|
|
61
|
+
editable, reorderable, and deletable until the worker claims them. Steering is
|
|
62
|
+
the explicit alternative: deliver now by interrupting the running turn.
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
// Queue (default): stacks behind the running turn.
|
|
66
|
+
await client.sendMessage(workspaceId, sessionId, "Also check the nginx config");
|
|
67
|
+
|
|
68
|
+
// Steer: send + promote to the queue front + interrupt the running turn.
|
|
69
|
+
await client.steerMessage(workspaceId, sessionId, "Stop — prod is paging, look at that first");
|
|
70
|
+
|
|
71
|
+
// Manage the queue while it waits.
|
|
72
|
+
const turns = await client.listTurns(workspaceId, sessionId);
|
|
73
|
+
await client.updateQueuedTurn(workspaceId, sessionId, turnId, { prompt: "rewritten" });
|
|
74
|
+
await client.reorderQueuedTurns(workspaceId, sessionId, [turnB, turnA]);
|
|
75
|
+
await client.deleteQueuedTurn(workspaceId, sessionId, turnId);
|
|
76
|
+
|
|
77
|
+
// Control events.
|
|
78
|
+
await client.interrupt(workspaceId, sessionId, { reason: "stop and report" });
|
|
79
|
+
await client.sendApprovalDecision(workspaceId, sessionId, { approvalId, decision: "approve" });
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Goals
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
const goal = await client.getGoal(workspaceId, sessionId); // counters: autoContinuations, noProgressStreak
|
|
86
|
+
await client.pauseGoal(workspaceId, sessionId, { rationale: "manual review" });
|
|
87
|
+
await client.resumeGoal(workspaceId, sessionId); // resets counters, re-arms continuations
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Files
|
|
91
|
+
|
|
92
|
+
`uploadFile` wraps the three-step flow (begin → signed PUT → complete) in one
|
|
93
|
+
call; the lower-level steps are exported for resumable/custom flows.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
const file = await client.uploadFile(workspaceId, {
|
|
97
|
+
filename: "incident-notes.md",
|
|
98
|
+
contentType: "text/markdown",
|
|
99
|
+
data: notes, // string | Blob | ArrayBuffer | Uint8Array
|
|
100
|
+
});
|
|
101
|
+
const { url } = await client.createFileDownloadUrl(workspaceId, file.id);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Full API coverage
|
|
105
|
+
|
|
106
|
+
Every public endpoint group has typed methods:
|
|
107
|
+
|
|
108
|
+
| Group | Methods |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| Access + workspaces | `getAccessContext`, `listWorkspaces`, `createWorkspace`, `getWorkspace`, `updateWorkspace` |
|
|
111
|
+
| Sessions + events | `createSession`, `listSessions`, `getSession`, `listEvents`, `sendEvent`, `sendMessage`, `steerMessage`, `interrupt`, `sendApprovalDecision`, `streamEvents`, `openEventStream` |
|
|
112
|
+
| Turn queue | `listTurns`, `updateQueuedTurn`, `reorderQueuedTurns`, `deleteQueuedTurn` |
|
|
113
|
+
| Goal | `getGoal`, `updateGoal`, `pauseGoal`, `resumeGoal` |
|
|
114
|
+
| Scheduled tasks | `createScheduledTask`, `listScheduledTasks`, `getScheduledTask`, `updateScheduledTask`, `pauseScheduledTask`, `resumeScheduledTask`, `triggerScheduledTask`, `deleteScheduledTask`, `listScheduledTaskRuns` |
|
|
115
|
+
| Environments | `listEnvironments`, `createEnvironment`, `getEnvironment`, `updateEnvironment`, `deleteEnvironment`, `setEnvironmentVariable`, `deleteEnvironmentVariable` (values are write-only) |
|
|
116
|
+
| Files | `uploadFile`, `beginFileUpload`, `completeFileUpload`, `getFile`, `createFileDownloadUrl` |
|
|
117
|
+
| Documents | `createDocumentBase`, `listDocumentBases`, `getDocumentBase`, `addDocument`, `listDocuments`, `reindexDocument`, `searchDocuments` |
|
|
118
|
+
| Packs | `listPacks`, `registerPack`, `getPack`, `enablePack`, `deletePack`, `listPackInstallations` |
|
|
119
|
+
| Capabilities | `listCapabilities`, `createCapability`, `enableCapability`, `disableCapability`, `discoverMcpCapabilities` |
|
|
120
|
+
| GitHub | `getGitHubApp`, `githubConnectUrl`, `listGitHubRepositories`, `syncGitHubRepositories`, `createGitHubAppManifest` |
|
|
121
|
+
| API keys | `listApiKeys`, `createApiKey`, `deleteApiKey` |
|
|
122
|
+
| Billing | `getBilling`, `getBillingUsage`, `getBillingEntitlements`, `createBillingCheckout` |
|
|
123
|
+
|
|
124
|
+
## Proxy through your own API
|
|
125
|
+
|
|
126
|
+
Keep your OpenGeni API key on your server and re-emit the stream to your own
|
|
127
|
+
browser clients. The re-emitted wire format is identical to OpenGeni's SSE
|
|
128
|
+
stream, so the browser side can consume it with this same SDK (or a plain
|
|
129
|
+
`EventSource`), including resume via `?after=` / `Last-Event-ID`:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
// Your server (Hono, Next.js route handler, Bun.serve, workers, ...):
|
|
133
|
+
import { OpenGeniClient, proxySessionEventStream } from "@opengeni/sdk";
|
|
134
|
+
|
|
135
|
+
const client = new OpenGeniClient({ baseUrl, apiKey });
|
|
136
|
+
|
|
137
|
+
export function GET(request: Request): Response {
|
|
138
|
+
// authenticate *your* user, resolve their session id, then:
|
|
139
|
+
return proxySessionEventStream(client, workspaceId, sessionId, {
|
|
140
|
+
after: request, // honors ?after= and Last-Event-ID from the browser
|
|
141
|
+
signal: request.signal, // browser disconnect tears down the upstream stream
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
For custom layers, the pieces are exported individually:
|
|
147
|
+
`sessionEventsToSseStream`, `sessionEventsToSseResponse`, `formatSseEvent`,
|
|
148
|
+
`resumeSequenceFromRequest`, and `parseSseStream`.
|
|
149
|
+
|
|
150
|
+
## Types
|
|
151
|
+
|
|
152
|
+
The SDK ships hand-written mirrors of the public wire shapes (sessions, turns,
|
|
153
|
+
events, resource/tool refs) so it carries no runtime dependency on the server
|
|
154
|
+
packages. `test/contract-parity.test.ts` pins them to `@opengeni/contracts`,
|
|
155
|
+
so contract drift fails the repo gate instead of shipping. `SessionEvent.type`
|
|
156
|
+
is an open union: unknown event types from newer servers flow through instead
|
|
157
|
+
of breaking older SDK consumers.
|