@opengeni/sdk 0.52.1 → 1.0.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 +1078 -33
- package/dist/accounts.d.ts +153 -0
- package/dist/accounts.js +307 -0
- package/dist/accounts.js.map +1 -0
- package/dist/adapters/express.d.ts +31 -0
- package/dist/adapters/express.js +87 -0
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/hono.d.ts +16 -0
- package/dist/adapters/hono.js +8 -0
- package/dist/adapters/hono.js.map +1 -0
- package/dist/adapters/next.d.ts +29 -0
- package/dist/adapters/next.js +22 -0
- package/dist/adapters/next.js.map +1 -0
- package/dist/allowance-refusal.d.ts +2 -0
- package/dist/allowance-refusal.js +50 -0
- package/dist/allowance-refusal.js.map +1 -0
- package/dist/artifact-catalog.d.ts +31 -0
- package/dist/artifact-client.d.ts +23 -11
- package/dist/artifacts.d.ts +1 -1
- package/dist/artifacts.js +7 -5
- package/dist/automations-client.d.ts +17 -0
- package/dist/automations.d.ts +3 -0
- package/dist/automations.js +84 -0
- package/dist/automations.js.map +1 -0
- package/dist/browser.d.ts +11 -0
- package/dist/browser.js +47 -0
- package/dist/browser.js.map +1 -0
- package/dist/chat/fold.d.ts +75 -0
- package/dist/chat/handler.d.ts +41 -0
- package/dist/chat/http.d.ts +67 -0
- package/dist/chat/ids.d.ts +34 -0
- package/dist/chat/index.d.ts +9 -0
- package/dist/chat/index.js +1694 -0
- package/dist/chat/index.js.map +1 -0
- package/dist/chat/openai.d.ts +30 -0
- package/dist/chat/opengeni.d.ts +128 -0
- package/dist/chat/types.d.ts +207 -0
- package/dist/chat/vercel.d.ts +49 -0
- package/dist/chats.d.ts +8 -0
- package/dist/{chunk-KIHGPM7H.js → chunk-2UXZUCEZ.js} +309 -46
- package/dist/chunk-2UXZUCEZ.js.map +1 -0
- package/dist/chunk-4SMEOV3P.js +246 -0
- package/dist/chunk-4SMEOV3P.js.map +1 -0
- package/dist/{chunk-FHI4DFIG.js → chunk-AH5D5Q5V.js} +31 -35
- package/dist/chunk-AH5D5Q5V.js.map +1 -0
- package/dist/chunk-APXVSSSY.js +301 -0
- package/dist/chunk-APXVSSSY.js.map +1 -0
- package/dist/chunk-DUVIYER4.js +639 -0
- package/dist/chunk-DUVIYER4.js.map +1 -0
- package/dist/{chunk-ILQZR5N6.js → chunk-FG3OO5LM.js} +3484 -750
- package/dist/chunk-FG3OO5LM.js.map +1 -0
- package/dist/chunk-GZYZT22T.js +260 -0
- package/dist/chunk-GZYZT22T.js.map +1 -0
- package/dist/chunk-HMGADJGZ.js +150 -0
- package/dist/chunk-HMGADJGZ.js.map +1 -0
- package/dist/{chunk-DXDL7EEW.js → chunk-KWADQ227.js} +198 -114
- package/dist/chunk-KWADQ227.js.map +1 -0
- package/dist/chunk-LY2K6ELJ.js +126 -0
- package/dist/chunk-LY2K6ELJ.js.map +1 -0
- package/dist/chunk-MOTJYIYL.js +340 -0
- package/dist/chunk-MOTJYIYL.js.map +1 -0
- package/dist/{chunk-MZWTWOX6.js → chunk-NC57YMUO.js} +447 -3
- package/dist/chunk-NC57YMUO.js.map +1 -0
- package/dist/chunk-RWKB6YZG.js +1270 -0
- package/dist/chunk-RWKB6YZG.js.map +1 -0
- package/dist/chunk-U6CV2SXO.js +195 -0
- package/dist/chunk-U6CV2SXO.js.map +1 -0
- package/dist/chunk-UM4ZXXJR.js +9 -0
- package/dist/chunk-UM4ZXXJR.js.map +1 -0
- package/dist/chunk-V6GIY62B.js +229 -0
- package/dist/chunk-V6GIY62B.js.map +1 -0
- package/dist/chunk-WU6GQG25.js +268 -0
- package/dist/chunk-WU6GQG25.js.map +1 -0
- package/dist/{chunk-VWE2QIVO.js → chunk-XNG7YGR3.js} +23 -6
- package/dist/chunk-XNG7YGR3.js.map +1 -0
- package/dist/{chunk-FRJFNDIR.js → chunk-YIKA2QT4.js} +37 -2
- package/dist/chunk-YIKA2QT4.js.map +1 -0
- package/dist/chunk-ZKPY6YAJ.js +135 -0
- package/dist/chunk-ZKPY6YAJ.js.map +1 -0
- package/dist/client.d.ts +843 -107
- package/dist/codex-realtime-controller.d.ts +6 -0
- package/dist/codex-realtime-controller.js +2 -2
- package/dist/codex-realtime-v3.d.ts +13 -2
- package/dist/company-brain.d.ts +22 -0
- package/dist/company-profile.d.ts +14 -1
- package/dist/core.d.ts +9 -3
- package/dist/core.js +28 -9
- package/dist/deprecation.d.ts +47 -0
- package/dist/desktop.d.ts +8 -0
- package/dist/document-authority-client.d.ts +51 -0
- package/dist/document-authority.d.ts +2 -0
- package/dist/document-authority.js +12 -0
- package/dist/document-authority.js.map +1 -0
- package/dist/editable-artifacts/browser-session.d.ts +7 -3
- package/dist/editable-artifacts/controller.d.ts +4 -1
- package/dist/editable-artifacts/errors.d.ts +1 -1
- package/dist/editable-artifacts/http-live-transport.d.ts +6 -2
- package/dist/editable-artifacts/index.d.ts +2 -0
- package/dist/editable-artifacts/open-failure.d.ts +9 -0
- package/dist/editable-artifacts/types.d.ts +6 -3
- package/dist/editable-artifacts/worker/kernel-adapter.d.ts +1 -1
- package/dist/editable-artifacts-worker.js +44 -17
- package/dist/editable-artifacts-worker.js.map +1 -1
- package/dist/editable-artifacts.js +243 -124
- package/dist/editable-artifacts.js.map +1 -1
- package/dist/embedding-client.d.ts +129 -0
- package/dist/errors.d.ts +36 -1
- package/dist/feedback.d.ts +23 -0
- package/dist/gateway-realtime-transport.d.ts +1 -0
- package/dist/gateway-realtime-transport.js +5 -3
- package/dist/github-repositories.d.ts +10 -0
- package/dist/github-repositories.js +36 -0
- package/dist/github-repositories.js.map +1 -0
- package/dist/index.d.ts +61 -12
- package/dist/index.js +277 -126
- package/dist/index.js.map +1 -1
- package/dist/interaction-revision-stream.d.ts +11 -0
- package/dist/interaction.d.ts +687 -5
- package/dist/interaction.js +32 -1
- package/dist/knowledge.d.ts +7 -0
- package/dist/knowledge.js +20 -0
- package/dist/knowledge.js.map +1 -0
- package/dist/message-links.d.ts +68 -0
- package/dist/model-context.d.ts +68 -0
- package/dist/model-display.d.ts +2 -0
- package/dist/model-display.js +15 -0
- package/dist/model-display.js.map +1 -0
- package/dist/model-picker-order.d.ts +1 -0
- package/dist/model-picker-order.js +10 -0
- package/dist/model-picker-order.js.map +1 -0
- package/dist/organization-integration-policy.d.ts +26 -0
- package/dist/organization-integration-policy.js +26 -0
- package/dist/organization-integration-policy.js.map +1 -0
- package/dist/organization-private-session-settings.d.ts +6 -0
- package/dist/organization-private-session-settings.js +22 -0
- package/dist/organization-private-session-settings.js.map +1 -0
- package/dist/organization-user-setup.d.ts +8 -0
- package/dist/organization-user-setup.js +16 -0
- package/dist/organization-user-setup.js.map +1 -0
- package/dist/pr-review-client.d.ts +16 -0
- package/dist/pr-review.d.ts +4 -0
- package/dist/pr-review.js +62 -0
- package/dist/pr-review.js.map +1 -0
- package/dist/preference-registry.d.ts +9 -1
- package/dist/proxy.d.ts +7 -5
- package/dist/realtime.d.ts +6 -4
- package/dist/realtime.js +10 -6
- package/dist/realtime.js.map +1 -1
- package/dist/retained-artifacts.d.ts +5 -1
- package/dist/session-history-import.d.ts +22 -0
- package/dist/session-history-import.js +36 -0
- package/dist/session-history-import.js.map +1 -0
- package/dist/session-list-entries.d.ts +1 -0
- package/dist/session-list-entries.js +7 -0
- package/dist/session-list-entries.js.map +1 -0
- package/dist/session-message-search.d.ts +53 -0
- package/dist/session-proxy-client.d.ts +24 -0
- package/dist/session-proxy.d.ts +245 -0
- package/dist/session-proxy.js +24 -0
- package/dist/session-proxy.js.map +1 -0
- package/dist/session-titles.d.ts +2 -0
- package/dist/site-browser-entry.d.ts +1 -0
- package/dist/site-browser-runtime.gen.d.ts +1 -0
- package/dist/site-document.d.ts +5 -0
- package/dist/site-document.js +20 -0
- package/dist/site-document.js.map +1 -0
- package/dist/site-document.test.d.ts +1 -0
- package/dist/site-http.d.ts +16 -0
- package/dist/site-tool-bridge.d.ts +44 -0
- package/dist/site.d.ts +101 -0
- package/dist/site.js +49 -0
- package/dist/site.js.map +1 -0
- package/dist/skills.d.ts +75 -0
- package/dist/stream.d.ts +2 -0
- package/dist/tenant-workspaces.d.ts +47 -0
- package/dist/tenant-workspaces.js +8 -0
- package/dist/tenant-workspaces.js.map +1 -0
- package/dist/tool-auth.d.ts +87 -0
- package/dist/tool-auth.js +23 -0
- package/dist/tool-auth.js.map +1 -0
- package/dist/tool-display-metadata.d.ts +3 -0
- package/dist/tools-FXBLUWAE.js +12 -0
- package/dist/tools-FXBLUWAE.js.map +1 -0
- package/dist/tools.d.ts +61 -0
- package/dist/types.d.ts +3048 -406
- package/dist/usage-allowances.d.ts +36 -0
- package/dist/usage-allowances.js +80 -0
- package/dist/usage-allowances.js.map +1 -0
- package/dist/workspace-artifacts.d.ts +33 -5
- package/dist/workspace-instruction-policies.d.ts +24 -2
- package/dist/workspace-integrations.d.ts +74 -0
- package/dist/workspace-integrations.js +65 -0
- package/dist/workspace-integrations.js.map +1 -0
- package/dist/workspace-learning.d.ts +124 -0
- package/dist/workspace-live-stream.d.ts +14 -0
- package/dist/workspace-state.d.ts +8 -71
- package/package.json +108 -3
- package/src/accounts.ts +443 -0
- package/src/adapters/express.ts +136 -0
- package/src/adapters/hono.ts +18 -0
- package/src/adapters/next.ts +42 -0
- package/src/allowance-refusal.ts +5 -0
- package/src/artifact-catalog.ts +41 -0
- package/src/artifact-client.ts +43 -52
- package/src/artifacts.ts +7 -1
- package/src/automations-client.ts +124 -0
- package/src/automations.ts +15 -0
- package/src/browser.ts +54 -0
- package/src/chat/fold.ts +431 -0
- package/src/chat/handler.ts +240 -0
- package/src/chat/http.ts +257 -0
- package/src/chat/ids.ts +85 -0
- package/src/chat/index.ts +87 -0
- package/src/chat/openai.ts +439 -0
- package/src/chat/opengeni.ts +722 -0
- package/src/chat/types.ts +219 -0
- package/src/chat/vercel.ts +204 -0
- package/src/chats.ts +16 -0
- package/src/client.ts +5162 -792
- package/src/codex-realtime-controller.ts +260 -12
- package/src/codex-realtime-lifecycle.ts +1 -0
- package/src/codex-realtime-v3.ts +162 -31
- package/src/codex-realtime.ts +1 -1
- package/src/company-brain.ts +59 -0
- package/src/company-profile.ts +17 -1
- package/src/core.ts +23 -1
- package/src/deprecation.ts +149 -0
- package/src/desktop.ts +11 -1
- package/src/document-authority-client.ts +219 -0
- package/src/document-authority.ts +17 -0
- package/src/editable-artifacts/browser-session.ts +36 -22
- package/src/editable-artifacts/controller.ts +102 -47
- package/src/editable-artifacts/errors.ts +2 -0
- package/src/editable-artifacts/http-live-transport.ts +62 -37
- package/src/editable-artifacts/index.ts +6 -0
- package/src/editable-artifacts/open-failure.ts +79 -0
- package/src/editable-artifacts/storage.ts +31 -40
- package/src/editable-artifacts/types.ts +6 -3
- package/src/editable-artifacts/worker/kernel-adapter.ts +23 -7
- package/src/editable-artifacts/worker/runtime.ts +26 -13
- package/src/editable-artifacts/worker/wire-codec.ts +23 -5
- package/src/embedding-client.ts +519 -0
- package/src/errors.ts +185 -4
- package/src/feedback.ts +20 -0
- package/src/gateway-realtime-transport.ts +253 -110
- package/src/github-repositories.ts +60 -0
- package/src/index.ts +603 -54
- package/src/interaction-revision-stream.ts +117 -0
- package/src/interaction.ts +1367 -51
- package/src/knowledge.ts +72 -0
- package/src/message-links.ts +178 -0
- package/src/model-context.ts +112 -0
- package/src/model-display.ts +10 -0
- package/src/model-picker-order.ts +6 -0
- package/src/organization-integration-policy.ts +54 -0
- package/src/organization-private-session-settings.ts +33 -0
- package/src/organization-user-setup.ts +31 -0
- package/src/pr-review-client.ts +92 -0
- package/src/pr-review.ts +17 -0
- package/src/preference-registry.ts +11 -0
- package/src/proxy.ts +17 -6
- package/src/realtime.ts +15 -4
- package/src/retained-artifacts.ts +101 -0
- package/src/session-history-import.ts +84 -0
- package/src/session-list-entries.ts +1 -0
- package/src/session-message-search.ts +47 -0
- package/src/session-proxy-client.ts +64 -0
- package/src/session-proxy.ts +1473 -0
- package/src/session-titles.ts +10 -0
- package/src/site-browser-entry.ts +6 -0
- package/src/site-browser-runtime.gen.ts +2 -0
- package/src/site-document.test.ts +64 -0
- package/src/site-document.ts +18 -0
- package/src/site-http.ts +162 -0
- package/src/site-tool-bridge.ts +146 -0
- package/src/site.ts +585 -0
- package/src/skills.ts +69 -0
- package/src/sse.ts +8 -2
- package/src/stream.ts +38 -4
- package/src/tenant-workspaces.ts +148 -0
- package/src/tool-auth.ts +301 -0
- package/src/tool-display-metadata.ts +19 -0
- package/src/tools.ts +396 -0
- package/src/types.ts +3607 -493
- package/src/usage-allowances.ts +189 -0
- package/src/workspace-artifacts.ts +40 -6
- package/src/workspace-instruction-policies.ts +26 -1
- package/src/workspace-integrations.ts +402 -0
- package/src/workspace-learning.ts +138 -0
- package/src/workspace-live-stream.ts +137 -0
- package/src/workspace-state.ts +8 -82
- package/dist/chunk-DXDL7EEW.js.map +0 -1
- package/dist/chunk-FHI4DFIG.js.map +0 -1
- package/dist/chunk-FRJFNDIR.js.map +0 -1
- package/dist/chunk-ILQZR5N6.js.map +0 -1
- package/dist/chunk-KIHGPM7H.js.map +0 -1
- package/dist/chunk-MZWTWOX6.js.map +0 -1
- package/dist/chunk-V5F253OG.js +0 -116
- package/dist/chunk-V5F253OG.js.map +0 -1
- package/dist/chunk-VWE2QIVO.js.map +0 -1
package/README.md
CHANGED
|
@@ -5,13 +5,284 @@ session lifecycle, and the streaming core — SSE event streaming with automatic
|
|
|
5
5
|
reconnect, resume-by-sequence, gap backfill, and duplicate suppression — plus
|
|
6
6
|
helpers for proxying the stream through your own API.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
For the complete product boundary—organization API keys, organization
|
|
9
|
+
workspaces, Personal-workspace exclusion, tenant mapping, and external Skill
|
|
10
|
+
ownership—start with the canonical
|
|
11
|
+
[product integration guide](../../docs/product-integration.md).
|
|
12
|
+
|
|
13
|
+
Runtime dependencies are the published `@opengeni/contracts` wire schemas (which
|
|
14
|
+
bring Zod) and `@opengeni/connect`; nothing else. It needs only WHATWG `fetch`
|
|
15
|
+
and streams, so it runs in Node 18+, Bun, Deno, browsers, and edge runtimes.
|
|
16
|
+
The package is ESM-only (no `require` entry): use `import`, or `await import()`
|
|
17
|
+
from CommonJS.
|
|
18
|
+
|
|
19
|
+
Always pass `baseUrl` (for example `process.env.OPENGENI_API_BASE_URL`): the
|
|
20
|
+
chat facade defaults to production `https://app.opengeni.ai`, so omitting it
|
|
21
|
+
against a staging or self-hosted deployment silently talks to production.
|
|
22
|
+
|
|
23
|
+
Browser clients may call the public API from any origin with an explicitly safe
|
|
24
|
+
bearer design, but an organization API key belongs on the product server.
|
|
25
|
+
Browser cookies are accepted cross-origin only from operator-configured trusted
|
|
26
|
+
origins; arbitrary embedding origins never receive credentialed CORS responses.
|
|
27
|
+
|
|
28
|
+
## Allowance lifecycle recovery
|
|
29
|
+
|
|
30
|
+
`getWorkspaceAllowance(workspaceId)` still returns nullable configuration.
|
|
31
|
+
Use `getWorkspaceAllowanceState(workspaceId)` when you also need the lifecycle
|
|
32
|
+
version: `{ version: 0, config: null }` means never configured, while a positive
|
|
33
|
+
version with null configuration means cleared. Recreation uses that exact
|
|
34
|
+
version as `expectedVersion`.
|
|
35
|
+
|
|
36
|
+
`clearWorkspaceAllowance(workspaceId, { expectedVersion, operationId })` returns
|
|
37
|
+
`{ version }`. The optional operation ID makes a lost response recoverable:
|
|
38
|
+
reuse the same actor, operation ID and exact request. Replay rechecks current
|
|
39
|
+
authority and returns 409 after any later allowance lifecycle change. On 409,
|
|
40
|
+
read the current state and decide whether a new operation is intended; never
|
|
41
|
+
guess a CAS version. The SDK does not automatically retry mutations.
|
|
42
|
+
|
|
43
|
+
The same reads and administration are free functions on the
|
|
44
|
+
`@opengeni/sdk/usage-allowances` subpath for browser code that only has the
|
|
45
|
+
narrow `OpenGeniBrowserClient` (`getMyUsage(client, workspaceId)`,
|
|
46
|
+
`getAllUsage`, `setMemberAllowance`, ...); they call the same routes as the root methods.
|
|
47
|
+
|
|
48
|
+
State reads require the same budget-read authority as configuration reads.
|
|
49
|
+
Full usage additionally requires target workspace access; organization budget
|
|
50
|
+
authority alone does not expose the member roster. Agents cannot read or
|
|
51
|
+
change allowances.
|
|
52
|
+
|
|
53
|
+
## Embed the conversation (default)
|
|
54
|
+
|
|
55
|
+
Embedding Opengeni is one backend route plus one component, and you only speak
|
|
56
|
+
in your own ids. The browser renders `@opengeni/react`'s `OpenGeniChat` (or
|
|
57
|
+
`SessionConversation`); your server mounts `createSessionProxyHandler`, a
|
|
58
|
+
same-origin proxy that acts as your authenticated user:
|
|
10
59
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
60
|
+
```ts
|
|
61
|
+
// lib/opengeni.ts (server only: the organization API key never reaches the browser)
|
|
62
|
+
import { OpenGeni } from "@opengeni/sdk/chat";
|
|
63
|
+
|
|
64
|
+
export const og = new OpenGeni({ apiKey: process.env.OPENGENI_API_KEY! });
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
// app/api/opengeni/[...path]/route.ts (any web-standard host works; see the adapters below)
|
|
69
|
+
import { createSessionProxyRoute } from "@opengeni/sdk/next";
|
|
70
|
+
import { og } from "@/lib/opengeni";
|
|
71
|
+
|
|
72
|
+
export const dynamic = "force-dynamic";
|
|
73
|
+
export const { GET, POST, PUT, PATCH, DELETE } = createSessionProxyRoute(og, {
|
|
74
|
+
resolve: async (request) => {
|
|
75
|
+
const me = await authenticate(request); // your existing session check
|
|
76
|
+
if (!me) return new Response("Unauthorized", { status: 401 });
|
|
77
|
+
return { user: me.id, tenant: me.orgId }; // or { user: me.id }
|
|
78
|
+
},
|
|
79
|
+
createSession: (input) => input, // let the browser start chats
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
// app/assistant/assistant.tsx (browser): no provider, client, or workspace id.
|
|
85
|
+
"use client";
|
|
86
|
+
import { OpenGeniChat } from "@opengeni/react";
|
|
87
|
+
import "@opengeni/react/compiled.css";
|
|
88
|
+
|
|
89
|
+
export function Assistant() {
|
|
90
|
+
return <OpenGeniChat baseUrl="/api/opengeni" />; // chat list + conversation
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`baseUrl` makes the component create the browser client for your mount and use
|
|
95
|
+
the workspace the proxy resolved for the signed-in user (reported in the
|
|
96
|
+
proxy's client config). One conversation alone is
|
|
97
|
+
`<SessionConversation baseUrl="/api/opengeni" sessionId={id} />`. The
|
|
98
|
+
`<OpenGeniProvider client={client} workspaceId={workspaceId}>` form keeps
|
|
99
|
+
working, for example for several components or the headless hooks.
|
|
100
|
+
|
|
101
|
+
What `resolve` returns decides the workspace:
|
|
102
|
+
|
|
103
|
+
- `{ user, tenant }`: one workspace per tenant; chats are private per user by
|
|
104
|
+
default.
|
|
105
|
+
- `{ user }`: one workspace per user. It stays single-user: the SDK adds its
|
|
106
|
+
owner once, and Opengeni never admits anyone else to it.
|
|
107
|
+
- `{ user, workspaceId }`: bring your own workspace id.
|
|
108
|
+
|
|
109
|
+
Workspaces are created on first use and cached per `OpenGeni` instance.
|
|
110
|
+
`og.workspaceId({ tenant })` and `og.workspaceId({ user })` return the same ids
|
|
111
|
+
the proxy uses. There is no onboarding step: the first request a user makes
|
|
112
|
+
adds them to a tenant (or your own) workspace with conversation permissions
|
|
113
|
+
(workspace read, session create/read/control, file upload/read, and attaching
|
|
114
|
+
your per-session MCP servers; no admin). That needs an organization API key
|
|
115
|
+
with `members:manage` (full access includes it) plus those permissions.
|
|
116
|
+
Opengeni never changes an existing membership, so narrowing or widening one
|
|
117
|
+
with `updateExternalWorkspaceMember` sticks. A user you remove from a tenant
|
|
118
|
+
workspace is added again on their next request, because your product decides
|
|
119
|
+
who its users are; stop resolving a user you no longer admit. A removal from a
|
|
120
|
+
per-user workspace sticks. `organizationId` is optional:
|
|
121
|
+
the facade reads it once from the key. Keep `user` and `tenant`
|
|
122
|
+
host-authenticated, never from the request body or path.
|
|
123
|
+
|
|
124
|
+
The proxy and facade accept `chats`. `"private"` (the default with a user)
|
|
125
|
+
uses private visibility, session-only agent reach and personal Knowledge;
|
|
126
|
+
`"shared"` uses workspace visibility, agent reach and Knowledge, so a tenant's
|
|
127
|
+
users see each other's chats. Without a user and with `chats` omitted, the
|
|
128
|
+
facade keeps workspace visibility, session-only agent reach and Knowledge
|
|
129
|
+
authoring off. The API wire values are `visibility: "private" | "workspace"`;
|
|
130
|
+
stored visibility is `user_private | workspace_shared`. Explicit hook/create
|
|
131
|
+
fields override defaults. Keep MCP URLs and credentials in the server's
|
|
132
|
+
`createSession` hook; the browser cannot choose them through the proxy.
|
|
133
|
+
|
|
134
|
+
You can also pass a plain `OpenGeniClient` (organization key) instead of the
|
|
135
|
+
facade; then `resolve` must return `{ user, workspaceId }`. The explicit
|
|
136
|
+
membership APIs (`ensureWorkspace`, `addExternalWorkspaceMember`,
|
|
137
|
+
`updateExternalWorkspaceMember`, `cancelExternalWorkspaceMemberGrant`) keep
|
|
138
|
+
working for products that manage access themselves. Legacy
|
|
139
|
+
`chats: "isolated"` with `{ tenant, user }` still provisions a separate
|
|
140
|
+
workspace per user and tenant. Per-user workspaces add their owner with a
|
|
141
|
+
stable onboarding key, so a retry never restores a revoked grant;
|
|
142
|
+
`memberPermissions` replaces that owner's permissions on first onboarding
|
|
143
|
+
only. The standalone resolver is on the server-only
|
|
144
|
+
`@opengeni/sdk/tenant-workspaces` subpath.
|
|
145
|
+
|
|
146
|
+
The facade also accepts `agent` (identity, capabilities, instructions, renderer)
|
|
147
|
+
and defaults its renderer to `"markdown"`. Current servers always admit agent
|
|
148
|
+
configuration. For older servers that still gate it, only an implicit renderer
|
|
149
|
+
retries once without `agent` on `422 agent_config_not_enabled`, and that refusal
|
|
150
|
+
is cached per facade instance. Explicit agent settings are never stripped: the
|
|
151
|
+
422 names `OPENGENI_AGENT_CONFIG_ADMISSION_ENABLED` for that older server's
|
|
152
|
+
operator. The private default with a user now turns personal Knowledge
|
|
153
|
+
on; pass `memory: false` to keep authoring off.
|
|
154
|
+
Missing organization private-session enablement raises `OpenGeniSetupError`,
|
|
155
|
+
with owner/admin API, SDK and web-app instructions; the proxy preserves that
|
|
156
|
+
actionable error for browser clients.
|
|
157
|
+
|
|
158
|
+
Framework adapters are thin wrappers over the same web-standard handler (they
|
|
159
|
+
also accept `createChatHandler` or any `(Request) => Promise<Response>`):
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
// Next.js App Router: app/api/opengeni/[...path]/route.ts
|
|
163
|
+
import { createSessionProxyRoute } from "@opengeni/sdk/next";
|
|
164
|
+
export const dynamic = "force-dynamic";
|
|
165
|
+
export const { GET, POST, PUT, PATCH, DELETE } = createSessionProxyRoute(og, { resolve });
|
|
166
|
+
|
|
167
|
+
// Express / Connect / node:http (mount before body parsers, or they are re-serialized)
|
|
168
|
+
import { toNodeMiddleware } from "@opengeni/sdk/express";
|
|
169
|
+
app.use("/api/opengeni", toNodeMiddleware(handler));
|
|
170
|
+
|
|
171
|
+
// Hono
|
|
172
|
+
import { toHonoHandler } from "@opengeni/sdk/hono";
|
|
173
|
+
app.all("/api/opengeni/*", toHonoHandler(handler));
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The Next adapter passes the full URL, including any `basePath`; the Node
|
|
177
|
+
middleware rebuilds it from `originalUrl`, streams bodies and SSE, and aborts
|
|
178
|
+
the request when the client disconnects.
|
|
179
|
+
|
|
180
|
+
The chat list (`SessionList` / `OpenGeniChat`) uses `listSessionPage`. By
|
|
181
|
+
default (`sessionList: "mine"`) the proxy adds a creator filter for the
|
|
182
|
+
resolved user server-side, so each user sees only the chats they started;
|
|
183
|
+
`sessionList: "visible"` lists every chat OpenGeni lets the user read in the
|
|
184
|
+
workspace, and `false` disables listing. Archive and restore go through
|
|
185
|
+
`updateSessionArchive` unless `archive: false`. A "New chat" in `OpenGeniChat`
|
|
186
|
+
sends only `{ initialMessage, idempotencyKey }`, so it needs the
|
|
187
|
+
`createSession` hook.
|
|
188
|
+
|
|
189
|
+
`listSessionPage` includes personal pinned details by default. A caller with a
|
|
190
|
+
separate `pinsOnly: true` read can pass `includePinned: false` on ordinary pages
|
|
191
|
+
to avoid repeatedly loading those details. Ordinary pages still exclude pinned
|
|
192
|
+
rows; `pinsOnly: true` and `includePinned: false` cannot be combined. Older API
|
|
193
|
+
versions may ignore `includePinned` and return the default pin projection.
|
|
194
|
+
|
|
195
|
+
For navigation lists, `listSessionSummaryPage` uses the same filters, ordering,
|
|
196
|
+
pin rules and cursors, returning `SessionListEntry` records. Entries include
|
|
197
|
+
display/rename titles, creator, lifecycle, personal state and hierarchy counts;
|
|
198
|
+
prompts and execution configuration remain on `getSession`. Older APIs return
|
|
199
|
+
full pages that the SDK projects locally. Unknown filters/cursors still fail
|
|
200
|
+
explicitly rather than returning an unfiltered result.
|
|
201
|
+
|
|
202
|
+
Every request calls `resolve`, then runs through `asUser(user, { source })`
|
|
203
|
+
(there is no service-authority fallback) against exactly the resolved
|
|
204
|
+
workspace. Only the native routes `OpenGeniProvider` and the conversation use
|
|
205
|
+
are served: client config; workspace read, model catalog, live control stream,
|
|
206
|
+
and workspace Resume; session read/rename, events (list and SSE with
|
|
207
|
+
`Last-Event-ID` resume), send/steer/approval/human-input, queue, composer
|
|
208
|
+
draft, pause/resume; and, unless `files: false`, attachment upload and download
|
|
209
|
+
URLs. Every other route or method is a 404 (cancel and workspace Pause are
|
|
210
|
+
refused); unknown query parameters on served reads pass through. Browser session creation is disabled
|
|
211
|
+
unless you supply `createSession`; the browser may then send only
|
|
212
|
+
`initialMessage` and `idempotencyKey`, and your hook returns the full request
|
|
213
|
+
(tools, MCP servers, Skills, instructions, model policy). Message bodies are
|
|
214
|
+
capped by `maxBodyBytes` (1 MiB), cannot rotate MCP credentials or attach
|
|
215
|
+
anything but files, and `modelSelection: false` strips per-message model
|
|
216
|
+
choices. `authorizeSession(sessionId, context)` adds a product-level session
|
|
217
|
+
check on top of OpenGeni's own membership and visibility checks. Without
|
|
218
|
+
`authorizeMutation`, only cross-site (`Sec-Fetch-Site`) mutations are refused,
|
|
219
|
+
so cookie-authenticated hosts should pass their CSRF check.
|
|
220
|
+
|
|
221
|
+
### Your own tools, as the signed-in user
|
|
222
|
+
|
|
223
|
+
`toolServer` attaches your product's MCP endpoint to every session the
|
|
224
|
+
`createSession` hook creates, with a short-lived token for the user `resolve`
|
|
225
|
+
authenticated. The proxy refreshes it on every send, steer, submit, approval,
|
|
226
|
+
and human-input answer. Your endpoint, built with any MCP library, verifies it:
|
|
227
|
+
|
|
228
|
+
```ts
|
|
229
|
+
createSessionProxyHandler(og, {
|
|
230
|
+
resolve,
|
|
231
|
+
createSession,
|
|
232
|
+
toolServer: {
|
|
233
|
+
url: "https://app.example.com/api/mcp", // public HTTPS (tunnel locally)
|
|
234
|
+
approvals: { ask: ["rename_post"] }, // the user approves each call first
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// In the MCP route (Web Request, or an Express/Node request):
|
|
239
|
+
import { ToolRequestError, verifyToolRequest } from "@opengeni/sdk/tool-auth";
|
|
240
|
+
const { user, tenant, workspaceId } = await verifyToolRequest(request); // or ToolRequestError (401)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Scope every tool to the verified `user` and `tenant`; never trust ids the model
|
|
244
|
+
sends; list write tools in `approvals.ask`. Tools act as the chat's creator, also
|
|
245
|
+
in shared chats. Members need `mcp_servers:attach`. Tokens are HS256 JWTs signed
|
|
246
|
+
with a key derived from `OPENGENI_API_KEY` (or the same `secret` on both sides),
|
|
247
|
+
bound to the full tool URL by `aud` (set `OPENGENI_TOOL_SERVER_URL` to configure
|
|
248
|
+
both sides at once), and valid for `ttlSeconds` (default 24 hours). Non-Node
|
|
249
|
+
verifiers use the hex key from `deriveToolTokenKey()`, never the organization key;
|
|
250
|
+
the format is documented in `src/tool-auth.ts`. See
|
|
251
|
+
`examples/tool-server` for a runnable Express version.
|
|
252
|
+
|
|
253
|
+
`beforeForwardMessage(message, context)` runs before every forwarded user
|
|
254
|
+
message (send, steer, composer submit, and a browser-started create) and may
|
|
255
|
+
return server-owned additions, or a `Response` to refuse the message:
|
|
256
|
+
|
|
257
|
+
```ts
|
|
258
|
+
createSessionProxyHandler(og, {
|
|
259
|
+
resolve,
|
|
260
|
+
beforeForwardMessage: async ({ sessionId, delivery }, { user }) => ({
|
|
261
|
+
// Model-visible, placed before any context the browser sent.
|
|
262
|
+
modelContext: `Page ${currentPage(user)} · ${timeZone(user)} · ${today()}`,
|
|
263
|
+
// Header-only rotation of MCP servers already attached to the session,
|
|
264
|
+
// applied atomically as the message is accepted (ignored on create).
|
|
265
|
+
mcpCredentialUpdates: [
|
|
266
|
+
{ id: "acme", headers: { Authorization: `Bearer ${await mintUserToken(user)}` } },
|
|
267
|
+
],
|
|
268
|
+
}),
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
This is the per-user tool token pattern: create the session with
|
|
273
|
+
`mcpServers: [{ id: "acme", url, headers }]` (and `tools: [{ kind: "mcp", id:
|
|
274
|
+
"acme" }]`, which the acting user needs `mcp_servers:attach` to attach), then
|
|
275
|
+
hand the MCP server a fresh short-lived bearer on every message. The browser
|
|
276
|
+
still cannot send `mcpCredentialUpdates` itself. With `modelSelection: false`
|
|
277
|
+
the proxy reports `modelSelection: false` in the client config, and
|
|
278
|
+
`SessionConversation` hides its model picker.
|
|
279
|
+
|
|
280
|
+
Opengeni adds each user to the workspace on their first request (see
|
|
281
|
+
[External users](#external-users-asuser)), with `workspace:read`,
|
|
282
|
+
`sessions:create`, `sessions:read`, `sessions:control`, `files:upload`,
|
|
283
|
+
`files:read`, and `mcp_servers:attach`. Pass the facade from `@opengeni/sdk/chat`
|
|
284
|
+
instead of a client to resolve `{ tenant, user }` or `{ user }` to a workspace
|
|
285
|
+
created on first use.
|
|
15
286
|
|
|
16
287
|
## Quick start
|
|
17
288
|
|
|
@@ -19,7 +290,7 @@ credentialed CORS responses.
|
|
|
19
290
|
import { OpenGeniClient } from "@opengeni/sdk";
|
|
20
291
|
|
|
21
292
|
const client = new OpenGeniClient({
|
|
22
|
-
baseUrl:
|
|
293
|
+
baseUrl: process.env.OPENGENI_API_BASE_URL!,
|
|
23
294
|
apiKey: process.env.OPENGENI_API_KEY!,
|
|
24
295
|
});
|
|
25
296
|
|
|
@@ -37,6 +308,574 @@ for await (const event of client.streamEvents(workspaceId, session.id)) {
|
|
|
37
308
|
}
|
|
38
309
|
```
|
|
39
310
|
|
|
311
|
+
## Organization-backed product setup
|
|
312
|
+
|
|
313
|
+
A multi-tenant product should hold one organization API key on its backend and
|
|
314
|
+
idempotently map each product tenant to an OpenGeni organization workspace. The
|
|
315
|
+
wire kind for an organization workspace is `"shared"`; Personal workspaces are
|
|
316
|
+
excluded.
|
|
317
|
+
|
|
318
|
+
```ts
|
|
319
|
+
const client = new OpenGeniClient({
|
|
320
|
+
baseUrl: process.env.OPENGENI_API_BASE_URL!,
|
|
321
|
+
apiKey: process.env.OPENGENI_API_KEY!,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const organizationId = process.env.OPENGENI_ORGANIZATION_ID!;
|
|
325
|
+
const { workspace, created } = await client.ensureWorkspace({
|
|
326
|
+
accountId: organizationId,
|
|
327
|
+
externalSource: "acme-product",
|
|
328
|
+
externalId: tenant.id,
|
|
329
|
+
name: tenant.displayName,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
if (workspace.kind !== "shared") {
|
|
333
|
+
throw new Error("Product integrations require an organization workspace");
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
The response is `{ workspace, created }`. Replays return the original nested
|
|
338
|
+
workspace with `created: false`. For an organization key, `getAccessContext()`
|
|
339
|
+
does not enumerate every workspace grant; call `listWorkspaces()` for the
|
|
340
|
+
organization-workspace inventory allowed by the key's live scope.
|
|
341
|
+
|
|
342
|
+
Organization key administration uses `listOrganizationApiKeys`,
|
|
343
|
+
`getOrganizationApiKey`, `createOrganizationApiKey`, `updateOrganizationApiKey`,
|
|
344
|
+
and `deleteOrganizationApiKey`. GET detail and PATCH return the raw `ApiKey`;
|
|
345
|
+
creation returns `{ apiKey, token }`. The key token from a
|
|
346
|
+
create response is shown once and must be stored in the backend's secret
|
|
347
|
+
manager. `createOrganizationApiKey(organizationId, { name, access: "read" })`
|
|
348
|
+
mints a read-only master key: it inventories shared workspaces and reads their
|
|
349
|
+
sessions, events, and files, but cannot create sessions, send messages, or mint
|
|
350
|
+
keys. This legacy `access: "read"` tier is distinct from the broader explicit
|
|
351
|
+
`read_only` preset, which includes every read/list/view/search permission
|
|
352
|
+
except the secret-value reads `secrets:read` and `variable-sets:read`.
|
|
353
|
+
|
|
354
|
+
Create or edit with `policy: { preset, permissions, workspaceScope }` for exact
|
|
355
|
+
grants. `OrganizationAccessPreset` is `"read_only" | "full" | "custom"`;
|
|
356
|
+
the label never adds permissions and the server recomputes it from the selected
|
|
357
|
+
set. Explicit `full` selects every canonical non-deprecated permission; `custom`
|
|
358
|
+
has no implicit grants or scope. Even `workspace:admin` grants no wildcard under
|
|
359
|
+
an explicit policy. Scope is `{ kind: "all" }` (current and future shared
|
|
360
|
+
workspaces) or `{ kind: "selected", workspaceIds }` (up to 500 unique same-org
|
|
361
|
+
shared-workspace IDs; empty reaches none). Personal workspaces are always excluded.
|
|
362
|
+
|
|
363
|
+
`updateOrganizationApiKey(organizationId, apiKeyId, { name?, description?, policy? })`
|
|
364
|
+
requires at least one change; `description: null` clears it. Metadata-only edits
|
|
365
|
+
preserve legacy stored permissions and wildcard semantics. A policy edit replaces
|
|
366
|
+
the policy, transitions legacy keys to explicit semantics, and applies narrowing
|
|
367
|
+
on the next request without rotating the token. Omitting policy at creation
|
|
368
|
+
retains legacy `access`/`preset` inputs and the full legacy default.
|
|
369
|
+
See [the canonical policy guide](../../docs/product-integration.md#explicit-organization-key-policies).
|
|
370
|
+
|
|
371
|
+
Keys with session-read authority can call
|
|
372
|
+
`listOrganizationSessions(organizationId, { limit, cursor, scopeSubjectId, status })`
|
|
373
|
+
for one page of sessions across authorized shared workspaces (each row carries its
|
|
374
|
+
`workspaceId`; private sessions and Personal workspaces never appear), or
|
|
375
|
+
`iterateOrganizationSessions` to follow `nextCursor` to the end.
|
|
376
|
+
|
|
377
|
+
The external backend also owns its Skill catalog. Load the selected definitions
|
|
378
|
+
and pass them inline in `CreateSessionRequest.skills` for each product-created
|
|
379
|
+
session. There is no organization-wide Skill registry or Skill inheritance in
|
|
380
|
+
this integration contract. See the canonical guide for the complete route map,
|
|
381
|
+
security boundary, and delivery checklist.
|
|
382
|
+
|
|
383
|
+
Installed Skills can be workspace-managed or marked `session_selected`.
|
|
384
|
+
Session-selected guidance is exposed to no agent
|
|
385
|
+
until `createSession` names the reviewed immutable component in
|
|
386
|
+
`installedSkillIds`. OpenGeni copies that exact artifact into the new session,
|
|
387
|
+
so omitting the field from customer-facing creates is a real contamination
|
|
388
|
+
boundary rather than a prompt convention.
|
|
389
|
+
The ordered selection is part of keyed-create identity: reusing an
|
|
390
|
+
`idempotencyKey` with a different selection returns a conflict instead of the
|
|
391
|
+
session configured by the earlier request.
|
|
392
|
+
|
|
393
|
+
For session lists, `deriveSessionDisplayTitle(session)` returns the durable
|
|
394
|
+
agent/user title when available. While the automatic title is still pending, it
|
|
395
|
+
shows the first short, sensitive-safe line of the opening prompt, skipping an
|
|
396
|
+
unsafe pasted URL or identifier when later safe text exists. If no safe line is
|
|
397
|
+
available, a short session reference keeps rows distinguishable. The display
|
|
398
|
+
automatically yields to the later `session.title_set` value.
|
|
399
|
+
|
|
400
|
+
Browser consoles that do not need operator-only surfaces can import
|
|
401
|
+
`OpenGeniBrowserClient` from `@opengeni/sdk/browser`. Document authority migration
|
|
402
|
+
and Default-collection backfill methods stay available on the backward-compatible
|
|
403
|
+
root and `@opengeni/sdk/core` clients, or through the focused
|
|
404
|
+
`@opengeni/sdk/document-authority` entry:
|
|
405
|
+
|
|
406
|
+
```ts
|
|
407
|
+
import { OpenGeniDocumentAuthorityClient } from "@opengeni/sdk/document-authority";
|
|
408
|
+
|
|
409
|
+
const operatorClient = new OpenGeniDocumentAuthorityClient({
|
|
410
|
+
baseUrl: process.env.OPENGENI_API_BASE_URL!,
|
|
411
|
+
apiKey: process.env.OPENGENI_API_KEY!,
|
|
412
|
+
});
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
## External users (`asUser`)
|
|
416
|
+
|
|
417
|
+
On a product backend, `client.asUser(externalId, { source? })` returns a separate
|
|
418
|
+
client that sends the external actor assertion with the organization key. IDs
|
|
419
|
+
are opaque and case-sensitive; the default source is `default`. Keep the key and
|
|
420
|
+
this client server-side. Each request requires the intersection of the key's
|
|
421
|
+
permissions and the user's workspace membership. API-key rotation does not
|
|
422
|
+
change the external identity.
|
|
423
|
+
|
|
424
|
+
Membership is automatic: when an `asUser` request reaches a shared workspace of
|
|
425
|
+
the key's own organization and the user has no membership there, Opengeni adds
|
|
426
|
+
one with conversation permissions (workspace read, session create/read/control,
|
|
427
|
+
file upload/read, `mcp_servers:attach`) and continues the request. This needs
|
|
428
|
+
the same authority as explicit onboarding: the key holds `members:manage` (or a
|
|
429
|
+
legacy `workspace:admin`) and every one of those permissions, with the
|
|
430
|
+
workspace in its scope; otherwise the request is a `403` as before. It never
|
|
431
|
+
applies to linked native identities, Personal workspaces, SDK per-user
|
|
432
|
+
workspaces (`opengeni-sdk:user-isolation:*` external source), other
|
|
433
|
+
organizations, requests that need a permission outside those defaults, or any
|
|
434
|
+
non-key caller, and it never changes an existing membership. The identity is
|
|
435
|
+
re-checked as active under the same lock, and the grant writes the same
|
|
436
|
+
receipt and lifecycle event as explicit onboarding, attributed to the key. A
|
|
437
|
+
user removed from a shared workspace is added again on their next request; a
|
|
438
|
+
suspended or offboarded identity stays refused.
|
|
439
|
+
|
|
440
|
+
Use the service client—not an `asUser` client—to call
|
|
441
|
+
`addExternalWorkspaceMember(workspaceId, { identity: { externalId, source? }, permissions })`
|
|
442
|
+
to choose permissions up front. It requires `members:manage`, cannot grant more
|
|
443
|
+
than the key permits, excludes Personal workspaces, and refuses to overwrite an
|
|
444
|
+
existing membership with different permissions (`409`, also when automatic
|
|
445
|
+
membership already created it); change an existing member's permissions with the keyed
|
|
446
|
+
`updateExternalWorkspaceMember(organizationId, workspaceId, organizationMembershipId,
|
|
447
|
+
{ operationId, permissions })`, which never tears down work. `asUser` calls never
|
|
448
|
+
reactivate disabled identities.
|
|
449
|
+
|
|
450
|
+
Service `removeWorkspaceMember` supports external members through the existing
|
|
451
|
+
fenced workspace teardown. Account-wide `updateExternalIdentityMembership(
|
|
452
|
+
organizationId, organizationMembershipId, { kind, expectedAuthorizationRevision,
|
|
453
|
+
operationId, reason? })` requires explicit `account:admin` and supports suspend,
|
|
454
|
+
reactivate, and offboard. The membership ID is returned by onboarding; its initial
|
|
455
|
+
authorization revision is 1. Retain the returned revision for subsequent changes.
|
|
456
|
+
Reactivation restores admission, not revoked memberships or work. Offboarding
|
|
457
|
+
follows existing retention policy and cannot be reactivated through this API.
|
|
458
|
+
Native-user targets and `asUser` calls are rejected by this service endpoint.
|
|
459
|
+
|
|
460
|
+
An `asUser` client can discover/access its own provisioned Personal workspace
|
|
461
|
+
under the key's permission ceiling. Core private session operations retain
|
|
462
|
+
organization readiness/settings and explicit sharing acknowledgments; service
|
|
463
|
+
clients gain no Personal-workspace fallback.
|
|
464
|
+
|
|
465
|
+
Optional native-account delegation uses `beginIdentityLink`, authenticated native
|
|
466
|
+
`previewIdentityLink`/`confirmIdentityLink`, and explicit server-side
|
|
467
|
+
`asLinkedUser(externalId, { source?, linkId, expectedLinkRevision })`. Confirmation
|
|
468
|
+
requires the actual native login plus the one-time host challenge; an organization
|
|
469
|
+
key cannot confirm for the native user. `listIdentityLinks(workspaceId, cursor?)`
|
|
470
|
+
returns only the effective participant's links in that organization (50 per page).
|
|
471
|
+
`revokeIdentityLink` requires the observed revision. Linking never changes ordinary
|
|
472
|
+
`asUser`, merges histories or transfers credentials. Link-dependent accepted work
|
|
473
|
+
retains revocation checks across schedules and children. Short-lived inline MCP
|
|
474
|
+
credentials remain supported, and durable renewal stays opt-in.
|
|
475
|
+
|
|
476
|
+
Connect accepts an optional `installationTarget: { instanceKey, displayName,
|
|
477
|
+
expectedInstanceVersion? }`. Keep the exact observed version for an existing named
|
|
478
|
+
account. Setup freezes the target through callback and operation review; omitting
|
|
479
|
+
it creates an independent named account instead of overwriting a default instance.
|
|
480
|
+
|
|
481
|
+
## Chat facade fallback (`@opengeni/sdk/chat`)
|
|
482
|
+
|
|
483
|
+
Use this only when your product already has its own chat UI speaking Vercel
|
|
484
|
+
`useChat` or an OpenAI-shaped protocol and you want a compatible drop-in
|
|
485
|
+
backend, or for server-side bots (`og.chat(...).send()`). It is a text-only
|
|
486
|
+
projection: tool outputs are dropped (the Vercel adapter emits no tool parts
|
|
487
|
+
by default, and only `output: { status }` with `toolParts: true`), there are no
|
|
488
|
+
files, attachments, artifacts, or images,
|
|
489
|
+
no goals/queue/steer UI, and reopening restores only a text snapshot. For
|
|
490
|
+
anything else, use [the default conversation embed](#embed-the-conversation-default).
|
|
491
|
+
|
|
492
|
+
One option object per conversation, one server handler for your endpoint.
|
|
493
|
+
Tenants map to organization workspaces, conversations map to deterministic
|
|
494
|
+
sessions, and the organization API key never leaves your server.
|
|
495
|
+
|
|
496
|
+
```ts
|
|
497
|
+
import { OpenGeni, createChatHandler } from "@opengeni/sdk/chat";
|
|
498
|
+
|
|
499
|
+
const og = new OpenGeni({
|
|
500
|
+
baseUrl: process.env.OPENGENI_API_BASE_URL!, // omitted = production https://app.opengeni.ai
|
|
501
|
+
apiKey: process.env.OPENGENI_API_KEY!, // the organization id is derived from the key
|
|
502
|
+
source: "acme-app", // labels your product's tenants and users (default "app")
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
const chat = await og.chat({
|
|
506
|
+
tenant: "acme", // one workspace per customer, created on first use; omit for one per user
|
|
507
|
+
user: "u_42", // authenticated product user; added to the workspace on first use
|
|
508
|
+
conversation: "c_9", // stable conversation id, independent of the acting user
|
|
509
|
+
agentAccess: "session", // "session" (default) | "user" | "workspace"
|
|
510
|
+
memory: "user", // "user" | "workspace" | false; session-only agent reach defaults to false
|
|
511
|
+
create: { sandboxBackend: "none" }, // raw create-request passthrough for a pure chat
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
const reply = await chat.send("hello"); // creates the session on the first send
|
|
515
|
+
console.log(reply.text); // or String(reply); the answer, without progress commentary
|
|
516
|
+
|
|
517
|
+
// Per message: model policy and model-visible page context (not shown in the transcript).
|
|
518
|
+
await chat.send("Why did revenue dip?", {
|
|
519
|
+
model: "gpt-5.5",
|
|
520
|
+
reasoningEffort: "low",
|
|
521
|
+
modelContext: "Viewing dashboard 42, range 2026-09-01..2026-09-28",
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
for await (const chunk of chat.stream("and then?")) {
|
|
525
|
+
if (chunk.type === "text") process.stdout.write(chunk.text);
|
|
526
|
+
if (chunk.type === "pending") await chat.respond({ requestId: chunk.pending.requestId, decision: "approve" });
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Your endpoint. `resolve` is your auth hook: identity comes from the request
|
|
530
|
+
// you authenticated, never from the body. The handler reads the client's
|
|
531
|
+
// conversation id itself (x-opengeni-conversation header, or the wire format's
|
|
532
|
+
// own field) and authorizes as `user`; return `conversation` from resolve only
|
|
533
|
+
// when the host names it, which is required when there is no `user`.
|
|
534
|
+
export const handler = createChatHandler(og, {
|
|
535
|
+
resolve: async (request) => {
|
|
536
|
+
const session = await getSessionFromCookie(request);
|
|
537
|
+
if (!session) return new Response("Unauthorized", { status: 401 });
|
|
538
|
+
return { tenant: session.accountId, user: session.userId };
|
|
539
|
+
},
|
|
540
|
+
// format: "vercel" | "openai-chat" | "openai-responses" (default "native");
|
|
541
|
+
// a request may override it with the x-opengeni-chat-format header.
|
|
542
|
+
});
|
|
543
|
+
export const GET = handler; // conversation history, for restoring the chat on reload
|
|
544
|
+
export const POST = handler; // send a message, or answer a pending request at .../respond
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
Conversation IDs do not change with the acting user. Authorized collaborators
|
|
548
|
+
can use the same session; private visibility prevents access by other users.
|
|
549
|
+
Use `chatBySessionId` for existing and legacy user-namespaced conversations.
|
|
550
|
+
A user is added to the workspace on their first request (see
|
|
551
|
+
[External users](#external-users-asuser)). Without a `user`, the host must name the conversation
|
|
552
|
+
from `resolve`. The Vercel and OpenAI adapters send only the latest user
|
|
553
|
+
message; the earlier messages in that request are imported once, as context on
|
|
554
|
+
the first message of a conversation, after which OpenGeni owns the history.
|
|
555
|
+
|
|
556
|
+
Pick the isolation per session with `agentAccess` (which other sessions the
|
|
557
|
+
agent may reach) and `memory` (what it remembers), all inside one workspace that
|
|
558
|
+
shares the customer's documents, instructions, and integrations:
|
|
559
|
+
|
|
560
|
+
| Scenario | `agentAccess` | `memory` |
|
|
561
|
+
| ------------------------------------------------- | ------------- | ------------- |
|
|
562
|
+
| Agent confined to its chat tree (support desk) | `"session"` | `false` |
|
|
563
|
+
| One user's chats see each other, not other users' | `"user"` | `"user"` |
|
|
564
|
+
| Everything in the tenant shared | `"workspace"` | `"workspace"` |
|
|
565
|
+
| Shared agent access, no memory | any | `false` |
|
|
566
|
+
|
|
567
|
+
To keep an existing `useChat` route with its own request body, stream the chat
|
|
568
|
+
into your AI SDK response yourself. The Vercel format speaks the v1 UI message
|
|
569
|
+
stream used by AI SDK 5, 6, and 7 (tool approval requests need 6 or later):
|
|
570
|
+
|
|
571
|
+
```ts
|
|
572
|
+
import { createUIMessageStream, createUIMessageStreamResponse } from "ai";
|
|
573
|
+
import { uiMessageStreamParts } from "@opengeni/sdk/chat";
|
|
574
|
+
|
|
575
|
+
export async function POST(request: Request) {
|
|
576
|
+
const { messages, dashboardId } = await request.json(); // your own body
|
|
577
|
+
const me = await authenticate(request);
|
|
578
|
+
const chat = await og.chat({ tenant: me.accountId, user: me.userId, conversation: me.chatId });
|
|
579
|
+
const text = lastUserText(messages);
|
|
580
|
+
const stream = createUIMessageStream({
|
|
581
|
+
execute: async ({ writer }) => {
|
|
582
|
+
const chunks = chat.stream(text, { modelContext: `Dashboard ${dashboardId}` });
|
|
583
|
+
for await (const part of uiMessageStreamParts(chunks, { framing: false })) {
|
|
584
|
+
writer.write(part as never);
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
});
|
|
588
|
+
return createUIMessageStreamResponse({ stream });
|
|
589
|
+
}
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
OpenGeni's own tool activity is omitted by default because those tools are not
|
|
593
|
+
in your typed tool set; pass `toolParts: true` (or `createChatHandler(og, {
|
|
594
|
+
toolParts: true })`) to emit them as `dynamic`, provider-executed tool parts.
|
|
595
|
+
Pending approvals are always emitted as dynamic tool parts with a
|
|
596
|
+
`tool-approval-request`.
|
|
597
|
+
|
|
598
|
+
The chat handler is backend-only: connect a custom or compatible frontend to
|
|
599
|
+
its protocol. For the full React agent experience, use `SessionConversation`
|
|
600
|
+
with `OpenGeniClient` and authenticated session routes, not this simplified
|
|
601
|
+
chat protocol. The normal SDK preserves files, tools, approvals with policies,
|
|
602
|
+
forks, and realtime voice.
|
|
603
|
+
|
|
604
|
+
## Related-work discovery
|
|
605
|
+
|
|
606
|
+
`listAgentTopology` returns a bounded hierarchy page plus provider-neutral
|
|
607
|
+
`relatedWork` evidence. A text query searches durable semantic titles, active
|
|
608
|
+
goals, and typed work claims; it never searches the opening prompt. An exact
|
|
609
|
+
subject filter looks up one canonical typed identity:
|
|
610
|
+
|
|
611
|
+
```ts
|
|
612
|
+
const page = await client.listAgentTopology(workspaceId, {
|
|
613
|
+
query: "permission scoped discovery",
|
|
614
|
+
statuses: ["running", "requires_action"],
|
|
615
|
+
activeOnly: true,
|
|
616
|
+
recentHours: 72,
|
|
617
|
+
claimLimit: 4,
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
for (const session of page.sessions) {
|
|
621
|
+
if (!session.relatedWork.possibleOverlap) continue;
|
|
622
|
+
console.log(session.title, session.relatedWork.match, session.relatedWork.claims);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const exact = await client.listAgentTopology(workspaceId, {
|
|
626
|
+
subject: {
|
|
627
|
+
namespace: "github",
|
|
628
|
+
type: "pull_request",
|
|
629
|
+
canonicalKey: "acme/app#42",
|
|
630
|
+
},
|
|
631
|
+
});
|
|
632
|
+
```
|
|
633
|
+
|
|
634
|
+
The evidence is always advisory: `advisoryOnly` and `noAdditionalAccess` are
|
|
635
|
+
literal `true` fields, and receiving a row does not authorize detail, message,
|
|
636
|
+
or control operations. Preserve `nextCursor` with the exact same filters;
|
|
637
|
+
relevance cursors are filter- and activity-snapshot-bound. A topology response
|
|
638
|
+
may carry `humanAdvisoriesEnabled: false`, which lets a human-facing client hide
|
|
639
|
+
the advisory UI without changing stored evidence or API authority. See
|
|
640
|
+
[`docs/work-discovery.md`](../../docs/work-discovery.md) for ranking, bounds,
|
|
641
|
+
claim lifecycle, and rollout semantics.
|
|
642
|
+
|
|
643
|
+
## Full-history message search
|
|
644
|
+
|
|
645
|
+
`searchSessionMessages(workspaceId, { query, sessionId?, groupBy?, archiveStatus?, limit?,
|
|
646
|
+
cursor? }, { signal? })` searches durable user and **completed assistant** text,
|
|
647
|
+
including unloaded history. It returns every non-overlapping literal occurrence,
|
|
648
|
+
not tools, reasoning, model context, or delta-only assistant output.
|
|
649
|
+
|
|
650
|
+
Each match has a durable `eventId`/`sequence`, a UTF-16 `messageMatchOffset`, and
|
|
651
|
+
`snippet: { text, matchStart, matchEnd }` with original-text UTF-16 offsets.
|
|
652
|
+
Results include `nextCursor`, `hasMore`, cumulative `matchedMessageCount` and
|
|
653
|
+
`matchedOccurrenceCount`, `scannedMessages`, and `countIsExact`. Keep following
|
|
654
|
+
cursors even on empty pages while `hasMore` is true; counts are provisional until
|
|
655
|
+
exhaustion. Cancel superseded searches with the third argument's AbortSignal.
|
|
656
|
+
This is a live, authorized traversal rather than a frozen snapshot; restart to
|
|
657
|
+
refresh after concurrent history or visibility changes.
|
|
658
|
+
|
|
659
|
+
For workspace search, `groupBy: "session"` returns the first hit per matching
|
|
660
|
+
session and skips that session's remaining history, keeping prolific messages
|
|
661
|
+
from filling multiple picker pages. It cannot be combined with `sessionId`.
|
|
662
|
+
In grouped mode both matched counters count session representatives, not full
|
|
663
|
+
per-session message/occurrence totals; `scannedMessages` counts only visited
|
|
664
|
+
messages. Cursors bind the grouping mode. Omit it for every-occurrence Find.
|
|
665
|
+
|
|
666
|
+
The method and types are exported from the ordinary SDK and browser entry.
|
|
667
|
+
Use `listEventPage` around the hit's sequence for bounded context, and use the
|
|
668
|
+
returned search snippet when a large source message's ordinary projection does
|
|
669
|
+
not include the hit. Never download complete history to perform Find locally.
|
|
670
|
+
See [the API contract and bounds](../../docs/session-message-search.md).
|
|
671
|
+
|
|
672
|
+
## Session visibility and forks
|
|
673
|
+
|
|
674
|
+
For organizations with session-tenancy activation, a canonical managed-cookie
|
|
675
|
+
owner can change a fully quiescent session between private and workspace
|
|
676
|
+
visibility or create an independent same-workspace private or workspace-visible fork. API keys and
|
|
677
|
+
delegated/service bearers are intentionally not authority for these methods.
|
|
678
|
+
The browser client below intentionally omits `apiKey`; its same-origin request
|
|
679
|
+
carries the authenticated managed-session cookie.
|
|
680
|
+
|
|
681
|
+
```ts
|
|
682
|
+
const browserClient = new OpenGeniClient({ baseUrl: window.location.origin });
|
|
683
|
+
const current = await browserClient.getSession(workspaceId, sessionId);
|
|
684
|
+
if (!current.tenancy) throw new Error("Session tenancy is not activated");
|
|
685
|
+
|
|
686
|
+
await browserClient.updateSessionVisibility(workspaceId, sessionId, {
|
|
687
|
+
visibility: "private",
|
|
688
|
+
expectedAuthorityEpoch: current.tenancy.authorityEpoch,
|
|
689
|
+
idempotencyKey: crypto.randomUUID(),
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
const fork = await browserClient.forkSession(workspaceId, sessionId, {
|
|
693
|
+
idempotencyKey: crypto.randomUUID(),
|
|
694
|
+
visibility: "workspace",
|
|
695
|
+
// Required when private conversation content is copied into workspace scope.
|
|
696
|
+
workspaceSharedAcknowledged: current.tenancy.visibility === "private",
|
|
697
|
+
});
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
Retain each idempotency key until the request has a known outcome. If a
|
|
701
|
+
transport failure reports `outcomeUnknown`, retry the same operation with the
|
|
702
|
+
same key. An authority-epoch conflict requires a fresh `getSession` and a new
|
|
703
|
+
user decision. A quiescence conflict identifies the stable blocker after live
|
|
704
|
+
turns, goals, realtime, schedules, workspace writers, retained processes, and
|
|
705
|
+
sandbox access have been settled. Forks copy exact same-workspace durable
|
|
706
|
+
conversation content but no live turn, goal, credential, Connection/delegation,
|
|
707
|
+
personal grant, Variable Set, Sandbox Environment, MCP server configuration, process, sandbox
|
|
708
|
+
identity, pin, or workflow. Destination visibility and acknowledgement are
|
|
709
|
+
idempotency-bound.
|
|
710
|
+
|
|
711
|
+
## Archived session history import (server only)
|
|
712
|
+
|
|
713
|
+
Use `@opengeni/sdk/session-history-import` to migrate historical conversations
|
|
714
|
+
into read-only archives. These functions take `client` first, use its ordinary
|
|
715
|
+
`requestJson` transport, and are not eager client methods or root helper exports:
|
|
716
|
+
|
|
717
|
+
Create requires `sessions:create`; append requires `sessions:control` and the same
|
|
718
|
+
authenticated importer. With `asUser`, the user's permissions apply rather than
|
|
719
|
+
the organization key's permissions.
|
|
720
|
+
|
|
721
|
+
```ts
|
|
722
|
+
import { OpenGeniClient } from "@opengeni/sdk";
|
|
723
|
+
import {
|
|
724
|
+
importArchivedSession,
|
|
725
|
+
appendArchivedSessionEvents,
|
|
726
|
+
} from "@opengeni/sdk/session-history-import";
|
|
727
|
+
|
|
728
|
+
const og = new OpenGeniClient({
|
|
729
|
+
baseUrl: process.env.OPENGENI_API_BASE_URL!,
|
|
730
|
+
apiKey: process.env.OPENGENI_API_KEY!,
|
|
731
|
+
});
|
|
732
|
+
// Existing tenant mapping; the original creator is admitted on first use.
|
|
733
|
+
const actor = og.asUser(originalExternalUserId, { source });
|
|
734
|
+
const imported = await importArchivedSession(actor, workspaceId, {
|
|
735
|
+
importId: "embedded:thread-42", // Stable; persist the exact request before sending.
|
|
736
|
+
title: "Original conversation",
|
|
737
|
+
createdAt: "2025-04-01T09:30:00Z",
|
|
738
|
+
visibility: "user_private",
|
|
739
|
+
});
|
|
740
|
+
const appended = await appendArchivedSessionEvents(actor, workspaceId, imported.importId, {
|
|
741
|
+
batchId: "thread-42:batch-0001", // Stable; also persist this exact body first.
|
|
742
|
+
offset: imported.nextOffset,
|
|
743
|
+
events: [{
|
|
744
|
+
type: "agent.message.completed",
|
|
745
|
+
createdAt: "2025-04-01T09:31:00Z",
|
|
746
|
+
payload: { text: "Historical answer", channel: "final" },
|
|
747
|
+
}],
|
|
748
|
+
});
|
|
749
|
+
// Persist imported.session.id and appended.nextOffset in the migration ledger.
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
External mapping equivalents take `(client, source, externalId, ...)`:
|
|
753
|
+
`importExternalWorkspaceArchivedSession` and
|
|
754
|
+
`appendExternalWorkspaceArchivedSessionEvents`. These resolve an existing
|
|
755
|
+
mapping; neither import form creates a workspace or grants membership. A bare
|
|
756
|
+
organization key creates a shared, ownerless archive. Use `asUser` for the
|
|
757
|
+
verified creator/owner; private imports require existing private-session
|
|
758
|
+
enablement. Preserve source visibility rather than widening it on failure.
|
|
759
|
+
|
|
760
|
+
The subpath exports `ImportArchivedSessionRequest/Response`,
|
|
761
|
+
`AppendArchivedSessionEventsRequest/Response`, `ArchivedSessionImportEvent` and
|
|
762
|
+
`SessionImportedArchive` types. Requests accept at most 100 events / 1 MiB
|
|
763
|
+
serialized UTF-8 JSON and 256 KiB per event. IDs and titles are bounded to 200
|
|
764
|
+
characters. Import events default to `[]`; append batches must be non-empty.
|
|
765
|
+
Events carry `{ type, createdAt, turnId?, payload }`: a finite supported historical
|
|
766
|
+
type, ISO timestamp, optional UUID/null correlation and JSON-object payload.
|
|
767
|
+
Source timestamps support at most millisecond precision; normalize finer dates
|
|
768
|
+
explicitly and retain originals in the ledger. Negative-zero JSON is rejected.
|
|
769
|
+
|
|
770
|
+
Exact create replay returns `created: false`; exact append replay returns
|
|
771
|
+
`replayed: true`. `offset` is a zero-based imported-event count, not a timeline
|
|
772
|
+
sequence. Retain the exact actor, mapping, IDs, offset and bodies for uncertain
|
|
773
|
+
retries; new batches use the acknowledged `nextOffset`. Changed key reuse or
|
|
774
|
+
an out-of-order new offset returns 409. Helpers propagate `OpenGeniApiError`,
|
|
775
|
+
including `outcomeUnknown`, and do not automatically retry mutations.
|
|
776
|
+
|
|
777
|
+
Import is timeline-only: completed messages, calls/results and goals may be
|
|
778
|
+
historical facts, but no model-facing history, live goal, pending decision or
|
|
779
|
+
execution is restored. `session.importedArchive` has
|
|
780
|
+
`{ importId, importedAt, readOnly: true }`, independent of personal archive state.
|
|
781
|
+
Render through the unchanged `SessionConversation` and session proxy; never
|
|
782
|
+
offer Send or Steer. Continuing an imported archive is unsupported in v1.
|
|
783
|
+
The proxy does not expose import routes. Re-upload files through existing APIs
|
|
784
|
+
and replace references before import; preserve additional source metadata and
|
|
785
|
+
source/destination IDs in the host ledger. See
|
|
786
|
+
[Migrating from embedded OpenGeni](../../docs/product-integration.md#migrating-from-embedded-opengeni).
|
|
787
|
+
|
|
788
|
+
## Connected accounts
|
|
789
|
+
|
|
790
|
+
Authenticated messages use the initiating user's eligible connected accounts.
|
|
791
|
+
Conversation visibility does not share account access. Queued work, retries and
|
|
792
|
+
child work retain the initiating user. No per-conversation connection grant is
|
|
793
|
+
required. Multiple eligible accounts require an explicit account choice:
|
|
794
|
+
|
|
795
|
+
```ts
|
|
796
|
+
const accounts = await browserClient.listOwnConnectionAccounts(workspaceId);
|
|
797
|
+
// When choosing among multiple accounts, use the exact eligible server/account pair.
|
|
798
|
+
const selection = { serverId: "mail", connectionId: accounts[0]!.id };
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
General personal-resource grants for documents, variable sets and other resource
|
|
802
|
+
kinds remain available through the root/core SDK. They do not authorize native
|
|
803
|
+
connected accounts.
|
|
804
|
+
|
|
805
|
+
## Workspace credentials, webhooks, and sandbox image
|
|
806
|
+
|
|
807
|
+
Configure these with an organization key or workspace admin session. Secrets are
|
|
808
|
+
returned once; store them when you create the resource. Protocol and payloads:
|
|
809
|
+
[`docs/workspace-integrations.md`](../../docs/workspace-integrations.md).
|
|
810
|
+
|
|
811
|
+
```ts
|
|
812
|
+
const { secret: providerSecret } = await client.putWorkspaceCredentialProvider(workspaceId, {
|
|
813
|
+
url: "https://product.example/opengeni/credentials",
|
|
814
|
+
});
|
|
815
|
+
const { secret: webhookSecret } = await client.createWorkspaceWebhook(workspaceId, {
|
|
816
|
+
url: "https://product.example/opengeni/events",
|
|
817
|
+
eventTypes: ["turn.completed", "turn.failed"],
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
// In your HTTP handlers, verify the raw body before parsing it:
|
|
821
|
+
const { event } = await verifyWebhookEvent({ body: rawBody, headers, secret: webhookSecret });
|
|
822
|
+
const request = await verifyCredentialProviderRequest({
|
|
823
|
+
body: rawBody,
|
|
824
|
+
headers,
|
|
825
|
+
secret: providerSecret,
|
|
826
|
+
});
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
To check an endpoint now, `testWorkspaceWebhook(client, workspaceId, webhookId)` sends a
|
|
830
|
+
signed `webhook.test` event (acknowledge it with any 2xx) and
|
|
831
|
+
`testWorkspaceCredentialProvider(client, workspaceId)` sends a request with
|
|
832
|
+
`purpose: "test"`; both come from `@opengeni/sdk/workspace-integrations` and return
|
|
833
|
+
what the endpoint answered, naming returned credentials but never their values.
|
|
834
|
+
|
|
835
|
+
`listWorkspaceSandboxImages` returns the deployment's allowlisted images; set one
|
|
836
|
+
with `updateWorkspaceSettings(workspaceId, { defaultSandboxImage })`.
|
|
837
|
+
|
|
838
|
+
## Personal schedules
|
|
839
|
+
|
|
840
|
+
A schedule created by an authenticated human or their active agent records that
|
|
841
|
+
human as its immutable owner. Only that owner or their verified agent may edit,
|
|
842
|
+
run, pause or delete it. Each occurrence resolves the owner's current accounts;
|
|
843
|
+
accepted retries retain their original identity and account selection.
|
|
844
|
+
|
|
845
|
+
```ts
|
|
846
|
+
const task = await client.createScheduledTask(workspaceId, {
|
|
847
|
+
name: "Daily triage",
|
|
848
|
+
schedule: { type: "calendar", hour: 8, minute: 0, timeZone: "Europe/Oslo" },
|
|
849
|
+
runMode: "reusable_session",
|
|
850
|
+
agentConfig: { prompt: "Triage the new support issues" },
|
|
851
|
+
connectionAccounts: [selection],
|
|
852
|
+
});
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
Account selections narrow eligible accounts; they never transfer ownership.
|
|
856
|
+
Omitting `connectionAccounts` on update preserves the selection. Passing an
|
|
857
|
+
empty array clears explicit account choices without changing the schedule owner.
|
|
858
|
+
Service-owned schedules retain service execution and do not acquire a human's
|
|
859
|
+
personal accounts. Run history remains credential-free.
|
|
860
|
+
|
|
861
|
+
A schedule freezes its connectors, accounts and (when an agent created it)
|
|
862
|
+
OpenGeni tools. For the owner, `listScheduledTasks` and `getScheduledTask`
|
|
863
|
+
include a read-only `policyDrift` naming what is out of date, and
|
|
864
|
+
`listScheduledTaskRuns` includes `accessFailures` for runs that could not use a
|
|
865
|
+
connector. `listScheduledTaskAccessAttention` lists schedules whose latest run
|
|
866
|
+
failed that way, and schedules that cannot start because a chosen account can no
|
|
867
|
+
longer be used (`unavailableAccounts`, with a null `runId`). A signed-in owner
|
|
868
|
+
re-freezes with their current access through
|
|
869
|
+
`refreshScheduledTaskAccess(workspaceId, taskId, { executionDigest, leaveOut })`,
|
|
870
|
+
where the optional `leaveOut` keeps named default connectors or OpenGeni tools
|
|
871
|
+
off; API keys and agents cannot. See [`docs/scheduled-task-access.md`](../../docs/scheduled-task-access.md).
|
|
872
|
+
|
|
873
|
+
Deleting a task is externally idempotent and immediately removes it from live
|
|
874
|
+
lists and quota, but the server retains a tombstone plus run/session/turn audit
|
|
875
|
+
evidence until workspace/account retention cleanup. A stable `triggerId` is
|
|
876
|
+
still required when a caller wants manual-trigger retries to coalesce; ordinary
|
|
877
|
+
scheduled fires derive their producer identity from the Temporal fire workflow.
|
|
878
|
+
|
|
40
879
|
## Realtime browser controller (`@opengeni/sdk/realtime`)
|
|
41
880
|
|
|
42
881
|
The public realtime subpath owns the provider-neutral browser controller and
|
|
@@ -101,7 +940,7 @@ import {
|
|
|
101
940
|
createBrowserEditableArtifactSession,
|
|
102
941
|
} from "@opengeni/sdk/editable-artifacts";
|
|
103
942
|
|
|
104
|
-
const client = new OpenGeniClient({ baseUrl:
|
|
943
|
+
const client = new OpenGeniClient({ baseUrl: process.env.OPENGENI_API_BASE_URL! });
|
|
105
944
|
const artifact = await client.getEditableArtifact(workspaceId, artifactId, {
|
|
106
945
|
replicaId,
|
|
107
946
|
});
|
|
@@ -112,7 +951,7 @@ const kernels = {
|
|
|
112
951
|
} as const;
|
|
113
952
|
const { editableArtifactKernelRuntime } = await kernels[artifact.modality]();
|
|
114
953
|
const session = createBrowserEditableArtifactSession({
|
|
115
|
-
baseUrl:
|
|
954
|
+
baseUrl: process.env.OPENGENI_API_BASE_URL!,
|
|
116
955
|
workspaceId,
|
|
117
956
|
artifact,
|
|
118
957
|
storageAuthority,
|
|
@@ -139,6 +978,66 @@ and long-lived credentials are never placed in the WebSocket URL. Hosts may
|
|
|
139
978
|
inject both `fetch` and the WebSocket factory for authenticated proxies and
|
|
140
979
|
tests; negotiated frame and mutation limits are enforced before work is queued.
|
|
141
980
|
|
|
981
|
+
## Browser and computer interaction (`@opengeni/sdk/interaction`)
|
|
982
|
+
|
|
983
|
+
An interaction **resource** is a workspace-scoped, durable object with a stable
|
|
984
|
+
id and lifecycle, such as a `BrowserSession`, `ComputerSession`, browser
|
|
985
|
+
identity, network route, auth run, or pending human intervention. This use of
|
|
986
|
+
"resource" is unrelated to the file/repository `resources` attached to a chat
|
|
987
|
+
message. A **facade** is only the typed, resource-oriented SDK organization
|
|
988
|
+
shown below; it owns no state or authority of its own.
|
|
989
|
+
|
|
990
|
+
`OpenGeniClient.interaction` is the framework-free resource facade over the same
|
|
991
|
+
public BrowserSession, ComputerSession, identity, auth, network-route, and human
|
|
992
|
+
intervention APIs used by OpenGeni itself. It adds no second state or execution
|
|
993
|
+
path: facade methods call the ordinary typed transport, and every mutation still
|
|
994
|
+
uses the canonical generation fences, operation receipt, permission check, and
|
|
995
|
+
placement controller.
|
|
996
|
+
|
|
997
|
+
```ts
|
|
998
|
+
const browser = await client.interaction.browsers.currentOrOpen({
|
|
999
|
+
workspaceId,
|
|
1000
|
+
associationSessionId: session.id,
|
|
1001
|
+
initialUrl: "http://127.0.0.1:3000",
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
const { targets } = await browser.tabs.list();
|
|
1005
|
+
const target = targets.find((candidate) => candidate.selected) ?? targets[0]!;
|
|
1006
|
+
const page = await browser.observe(target.id);
|
|
1007
|
+
const screenshot = await browser.screenshot(target.id, { fullPage: true });
|
|
1008
|
+
// screenshot.data is bounded image bytes; mediaType and dimensions travel with it.
|
|
1009
|
+
|
|
1010
|
+
const receipt = await browser.act({
|
|
1011
|
+
operationId: crypto.randomUUID(),
|
|
1012
|
+
targetId: target.id,
|
|
1013
|
+
expectedTargetGeneration: target.targetGeneration,
|
|
1014
|
+
expectedDocumentGeneration: target.documentGeneration,
|
|
1015
|
+
expectedFrameId: page.frameId,
|
|
1016
|
+
action: { type: "click", locator: { kind: "role", role: "button", name: "Continue" } },
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
if (receipt.state === "outcome_unknown") {
|
|
1020
|
+
// Do not replay a mutation blindly. Re-observe, then inspect this exact receipt.
|
|
1021
|
+
console.log(await browser.receipt(receipt.operationId));
|
|
1022
|
+
}
|
|
1023
|
+
```
|
|
1024
|
+
|
|
1025
|
+
`browser.targetState(targetId)` reads generation fences without an accessibility
|
|
1026
|
+
tree. `browser.readDom(targetId, request)` returns a bounded element or count
|
|
1027
|
+
query using those fences; sensitive values are redacted.
|
|
1028
|
+
|
|
1029
|
+
Browser identities are immutable version graphs: live browser state stays
|
|
1030
|
+
private until `browser.publishRevision(...)` explicitly creates a new revision.
|
|
1031
|
+
Protected auth fills resolve connection secrets only inside the broker/controller
|
|
1032
|
+
path; normal SDK reads, observations, diagnostics, receipts, and logs never
|
|
1033
|
+
return credential values. `client.openWorkspaceInteractionRevisionStream(...)`
|
|
1034
|
+
is the low-volume latest-wins invalidation stream for keeping resource catalogs
|
|
1035
|
+
fresh without attaching them to an inference/session event stream.
|
|
1036
|
+
|
|
1037
|
+
Use the flat `OpenGeniClient` interaction methods when implementing a proxy or
|
|
1038
|
+
custom transport. Use `client.interaction` for application logic. Both are the
|
|
1039
|
+
same API and authority boundary.
|
|
1040
|
+
|
|
142
1041
|
## Workspace artifacts
|
|
143
1042
|
|
|
144
1043
|
Workspace artifacts are generic, immutable HTML publications. The SDK does not
|
|
@@ -146,10 +1045,13 @@ assign product types such as app, page, dashboard, or gallery. List pages are
|
|
|
146
1045
|
bounded and expose both `truncated` and an opaque `nextCursor` so callers never
|
|
147
1046
|
mistake a partial page for the complete workspace catalog.
|
|
148
1047
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
1048
|
+
Each Site version retains one self-contained executable HTML runtime, its
|
|
1049
|
+
bounded editable source bundle, and the exact canonical tool identities it
|
|
1050
|
+
requested. The web host renders that runtime in an opaque-origin sandboxed
|
|
1051
|
+
iframe. Credentials, cookies, API URLs, workspace ids, and parent DOM authority
|
|
1052
|
+
never enter the iframe; `@opengeni/sdk/site` uses a single parent-owned
|
|
1053
|
+
`MessagePort` that exposes only the retained tool allowlist intersected with the
|
|
1054
|
+
current viewer's live workspace catalog.
|
|
153
1055
|
|
|
154
1056
|
```ts
|
|
155
1057
|
let cursor: string | undefined;
|
|
@@ -169,6 +1071,64 @@ return the exact source session, turn, attempt, and execution generation that
|
|
|
169
1071
|
published them. Version and event history are bounded; inspect
|
|
170
1072
|
`versionsTruncated` and `eventsTruncated` on the detail response.
|
|
171
1073
|
|
|
1074
|
+
## Workspace tools
|
|
1075
|
+
|
|
1076
|
+
The browser-safe SDK exposes the same gateway catalog and executor used by
|
|
1077
|
+
model MCP calls and Codemode. Generated declarations augment the lazy namespace
|
|
1078
|
+
for exact installed-tool types; canonical `{ serverId, toolName }` identity and
|
|
1079
|
+
the server-side catalog remain authoritative.
|
|
1080
|
+
|
|
1081
|
+
```ts
|
|
1082
|
+
const tools = client.tools.forWorkspace(workspaceId);
|
|
1083
|
+
const documents = await tools.docs.search({ query: "launch plan" });
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
An approval-required call needs a server-issued capability. A trusted host
|
|
1087
|
+
shows its approval UI first, then asks for one token bound to the current human,
|
|
1088
|
+
workspace, operation id, catalog digest, exact tool identity, and arguments.
|
|
1089
|
+
The token expires after five minutes, is stored only as a hash, and is consumed
|
|
1090
|
+
once by the matching call.
|
|
1091
|
+
|
|
1092
|
+
Connection-backed tools configured for both provider policy and one-shot human
|
|
1093
|
+
approval are omitted from this HTTP catalog until their provider adapter can
|
|
1094
|
+
preflight credential and resource authorization before consuming the token.
|
|
1095
|
+
|
|
1096
|
+
```ts
|
|
1097
|
+
const operationId = crypto.randomUUID();
|
|
1098
|
+
const identity = { serverId: "linear", toolName: "issues_update" };
|
|
1099
|
+
const input = { issueId, state: "Done" };
|
|
1100
|
+
const approval = await tools.$approve(identity, input, { operationId });
|
|
1101
|
+
|
|
1102
|
+
await tools.$call(identity, input, {
|
|
1103
|
+
operationId,
|
|
1104
|
+
approvalToken: approval.approvalToken,
|
|
1105
|
+
});
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
When a call or approval request receives the typed
|
|
1109
|
+
`409 details.code = "catalog_stale"` response, the SDK refreshes the catalog,
|
|
1110
|
+
re-resolves the exact identity or generated namespace path, preserves the
|
|
1111
|
+
operation id (generating it before the first attempt when omitted), and retries
|
|
1112
|
+
once. A call that already carries an approval token cannot reuse that
|
|
1113
|
+
single-use capability after the catalog digest changes; instead it throws
|
|
1114
|
+
`OpenGeniToolReapprovalRequiredError` with the refreshed identity and digest so
|
|
1115
|
+
the trusted host can show approval again for the same operation id.
|
|
1116
|
+
|
|
1117
|
+
Reapproval is valid only before the original capability is consumed. Once a
|
|
1118
|
+
call crosses that boundary, the server retains a hash-only tombstone for the
|
|
1119
|
+
operation id and rejects another approval with
|
|
1120
|
+
`409 details.code = "tool_gateway_operation_already_started"` and
|
|
1121
|
+
`outcomeUnknown: true`. The host must reconcile the provider outcome; it must
|
|
1122
|
+
not turn an ambiguous call into a second approved execution by minting a fresh
|
|
1123
|
+
operation id automatically.
|
|
1124
|
+
|
|
1125
|
+
Do not request approval capabilities speculatively or expose them to Site iframe
|
|
1126
|
+
code. The host projects only the active immutable version's requested identities,
|
|
1127
|
+
supplies the Site context on direct calls, and the server revalidates the active
|
|
1128
|
+
version and viewer's live authority. The requested set is a maximum allowlist,
|
|
1129
|
+
not an authority grant; ordinary gateway approval still applies. Archived Sites
|
|
1130
|
+
receive no tool bridge.
|
|
1131
|
+
|
|
172
1132
|
Omit `firstPartyMcpTools` for the complete OpenGeni tool catalog. An explicit
|
|
173
1133
|
`[]` exposes no broad first-party tools; attached resources and separately
|
|
174
1134
|
selected `files`/`docs` MCP servers are unaffected.
|
|
@@ -346,11 +1306,70 @@ await client.cancelSession(workspaceId, sessionId, {
|
|
|
346
1306
|
reason: "host record deleted",
|
|
347
1307
|
clientEventId: crypto.randomUUID(),
|
|
348
1308
|
});
|
|
1309
|
+
// approvalId is `approvals[].id` from the latest `session.requiresAction`
|
|
1310
|
+
// event (type SessionApprovalRequest: { id, name, arguments }); it is the
|
|
1311
|
+
// pending tool call id, not the event id.
|
|
349
1312
|
await client.sendApprovalDecision(workspaceId, sessionId, { approvalId, decision: "approve" });
|
|
350
1313
|
```
|
|
351
1314
|
|
|
1315
|
+
## Retry a failed session without changing its intent
|
|
1316
|
+
|
|
1317
|
+
Try again is separate from Pause/Resume and `sendMessage`. Retain the request
|
|
1318
|
+
across an ambiguous transport response; never synthesize a continuation prompt:
|
|
1319
|
+
|
|
1320
|
+
```ts
|
|
1321
|
+
const retryRequest = {
|
|
1322
|
+
clientEventId: crypto.randomUUID(),
|
|
1323
|
+
failureEventId, // the current durable turn.failed event id
|
|
1324
|
+
model: selectedModel,
|
|
1325
|
+
reasoningEffort: "high" as const,
|
|
1326
|
+
latencyMode: "standard" as const,
|
|
1327
|
+
};
|
|
1328
|
+
const retry = await client.retrySession(workspaceId, sessionId, retryRequest);
|
|
1329
|
+
// { outcome: "accepted" | "replayed", turnId, failureEventId }
|
|
1330
|
+
```
|
|
1331
|
+
|
|
1332
|
+
Requires `sessions:control` and session access. Recovery keeps the logical turn,
|
|
1333
|
+
original question, frozen authority, and completed history/tool results; it
|
|
1334
|
+
does not create a user message. Omitted policy fields retain the failed turn's
|
|
1335
|
+
selection. The selected policy is revalidated for new admission. A duplicate
|
|
1336
|
+
operation replays its receipt even after execution advances.
|
|
1337
|
+
|
|
1338
|
+
HTTP 409 codes distinguish `RETRY_STALE_FAILURE`, `RETRY_EXECUTION_UNRESOLVED`,
|
|
1339
|
+
`RETRY_PAUSED`, `RETRY_UNSUPPORTED_FAILURE`, and `IDEMPOTENCY_KEY_REUSED`.
|
|
1340
|
+
Unresolved tool outcomes cannot be blindly replayed. A deliberate Pause must
|
|
1341
|
+
be resumed separately. Failure without a retained logical turn, a settled
|
|
1342
|
+
scheduled occurrence, and idle credit exhaustion are not supported retry
|
|
1343
|
+
boundaries. See [run lifecycle](../../docs/run-lifecycle.md).
|
|
1344
|
+
|
|
352
1345
|
## Session tool policy and native web search
|
|
353
1346
|
|
|
1347
|
+
For standalone credential maintenance, use the dedicated operation rather than
|
|
1348
|
+
sending a message:
|
|
1349
|
+
|
|
1350
|
+
```ts
|
|
1351
|
+
const request = {
|
|
1352
|
+
operationKey: crypto.randomUUID(),
|
|
1353
|
+
updates: [{
|
|
1354
|
+
id: "crm",
|
|
1355
|
+
expectedCredentialVersion: 1,
|
|
1356
|
+
expectedServerUrl: "https://tools.example.test/mcp",
|
|
1357
|
+
headers: { Authorization: `Bearer ${replacementToken}` },
|
|
1358
|
+
}],
|
|
1359
|
+
};
|
|
1360
|
+
const receipt = await client.rotateSessionMcpCredentials(workspaceId, sessionId, request);
|
|
1361
|
+
```
|
|
1362
|
+
|
|
1363
|
+
Only exact existing inline servers without `connectionRef` are eligible. Both
|
|
1364
|
+
session control and MCP attach permissions, live session authorization, expected
|
|
1365
|
+
versions, and a quiescent credential-consumer boundary are required. This never
|
|
1366
|
+
sends a message, starts work, retries a failed turn, or refreshes an active
|
|
1367
|
+
client. Reconcile an ambiguous response with the same request/key; the SDK does
|
|
1368
|
+
not retry mutations automatically. Exact authorized replay returns the original
|
|
1369
|
+
receipt without a second write. Old receipt verification fails explicitly with
|
|
1370
|
+
503 if the deployment encryption key has been replaced. See the full
|
|
1371
|
+
[rotation contract](../../docs/session-mcp-servers.md#standalone-inline-credential-rotation).
|
|
1372
|
+
|
|
354
1373
|
Omitting `tools` when creating a top-level session selects the current
|
|
355
1374
|
workspace-default capability policy, including the built-in `files` server.
|
|
356
1375
|
Passing `tools`, including `[]`, is an intentional fixed narrowing and can
|
|
@@ -393,6 +1412,17 @@ it is not public web search.
|
|
|
393
1412
|
const goal = await client.getGoal(workspaceId, sessionId); // counters: autoContinuations, noProgressStreak
|
|
394
1413
|
await client.pauseGoal(workspaceId, sessionId, { rationale: "manual review" });
|
|
395
1414
|
await client.resumeGoal(workspaceId, sessionId); // resets counters, re-arms continuations
|
|
1415
|
+
|
|
1416
|
+
const revisions = await client.listGoalRevisions(workspaceId, sessionId); // legacy raw array
|
|
1417
|
+
const page = await client.listGoalRevisionPage(workspaceId, sessionId, { limit: 25 });
|
|
1418
|
+
await client.rejectGoalRevision(workspaceId, sessionId, proposalId, {
|
|
1419
|
+
expectedObjectiveRevision: goal.objectiveRevision,
|
|
1420
|
+
rationale: "Keep the existing objective",
|
|
1421
|
+
});
|
|
1422
|
+
await client.rollbackGoalRevision(workspaceId, sessionId, appliedRevisionId, {
|
|
1423
|
+
expectedObjectiveRevision: goal.objectiveRevision,
|
|
1424
|
+
rationale: "Restore the last known-good objective",
|
|
1425
|
+
});
|
|
396
1426
|
```
|
|
397
1427
|
|
|
398
1428
|
## Files
|
|
@@ -472,25 +1502,25 @@ Every public endpoint group has typed methods:
|
|
|
472
1502
|
|
|
473
1503
|
| Group | Methods |
|
|
474
1504
|
| --- | --- |
|
|
475
|
-
| Access + workspaces | `getAccessContext`, `listWorkspaces`, `createWorkspace`, `getWorkspace`, `updateWorkspace` |
|
|
1505
|
+
| Access + workspaces | `getAccessContext`, `listWorkspaces`, `createWorkspace`, `ensureWorkspace`, `getWorkspace`, `updateWorkspace` |
|
|
1506
|
+
| Managed organizations | `createOrganization` for first-time compatibility; `createAdditionalOrganization` for an already-onboarded human; `listOrganizationMemberships`, `listOrganizationInvitations`, `acceptOrganizationInvitation` |
|
|
476
1507
|
| Sessions + events | `createSession`, `listSessions`, `listSessionPage`, `listAgentTopology`, `getSession`, `getSessionLineage`, `updateSession`, `listEvents`, `sendEvent`, `sendMessage`, `steerMessage`, `pauseSession`, `resumeSession`, `cancelSession`, `sendApprovalDecision`, `streamEvents`, `openEventStream` |
|
|
477
1508
|
| Machines (bring-your-own-compute) | `listMachines`, `machineMetricsSeries`, `swapActiveSandbox`, `mintEnrollToken`, `lookupDeviceEnrollment`, `approveDeviceEnrollment`, `denyDeviceEnrollment` |
|
|
478
1509
|
| Turn queue | `getQueue`, `moveQueueItem`, `editQueueItem`, `steerQueueItem`, `deleteQueueItem` |
|
|
479
|
-
| Goal | `getGoal`, `updateGoal`, `pauseGoal`, `resumeGoal` |
|
|
480
|
-
| Scheduled tasks | `createScheduledTask`, `listScheduledTasks`, `getScheduledTask`, `updateScheduledTask`, `pauseScheduledTask`, `resumeScheduledTask`, `triggerScheduledTask`, `deleteScheduledTask`, `listScheduledTaskRuns` |
|
|
1510
|
+
| Goal | `getGoal`, `updateGoal`, `pauseGoal`, `resumeGoal`, `listGoalRevisions`, `listGoalRevisionPage`, `applyGoalRevision`, `rejectGoalRevision`, `rollbackGoalRevision` |
|
|
1511
|
+
| Scheduled tasks | `createScheduledTask`, `listScheduledTasks`, `getScheduledTask`, `updateScheduledTask`, `pauseScheduledTask`, `resumeScheduledTask`, `triggerScheduledTask`, `deleteScheduledTask`, `listScheduledTaskRuns`, `refreshScheduledTaskAccess`, `listScheduledTaskAccessAttention` |
|
|
481
1512
|
| Variable sets | `listVariableSets`, `createVariableSet`, `getVariableSet`, `updateVariableSet`, `deleteVariableSet`, `setVariableSetVariable`, `deleteVariableSetVariable`; generic reads are metadata-only, while dedicated permissioned exact-value reads are part of the held client train |
|
|
482
1513
|
| Files | `uploadFile`, `beginFileUpload`, `completeFileUpload`, `getFile`, `createFileDownloadUrl` |
|
|
483
1514
|
| Documents | `createDocumentBase`, `listDocumentBases`, `getDocumentBase`, `addDocument`, `listDocuments`, `reindexDocument`, `searchDocuments`, `searchKnowledge` (effective organization + workspace + immutable initiating-user personal scope) |
|
|
484
|
-
| Packs | `listPacks`, `registerPack`, `getPack`, `enablePack`, `deletePack`, `listPackInstallations` |
|
|
485
1515
|
| Capabilities | `listCapabilities`, `createCapability`, `enableCapability`, `disableCapability`, `discoverMcpCapabilities` |
|
|
486
1516
|
| Plugin packages | `previewPlugin`, `installPlugin`, `previewPluginUninstall`, `uninstallPlugin` |
|
|
487
|
-
| API Integrations | `
|
|
1517
|
+
| API Integrations | `listIntegrationDefinitions`, `listApiIntegrations`, `previewApiIntegration`, `startApiIntegrationOAuth`, `installApiIntegration`, `previewApiIntegrationUninstall`, `uninstallApiIntegration`, `listIntegrationFacets`, `configureIntegrationFacet`, `pauseIntegrationFacet`, `resumeIntegrationFacet`, `removeIntegrationFacet`, `browseGoogleDriveFacetSource`, `saveGoogleDriveFacetSource` |
|
|
488
1518
|
| Remote Skills | `previewSkillImport`, `installSkill`, `previewSkillUninstall`, `uninstallSkill` |
|
|
489
1519
|
| GitHub | `getGitHubApp`, `githubConnectUrl`, `listGitHubRepositories`, `syncGitHubRepositories`, `createGitHubAppManifest` |
|
|
490
|
-
| API keys | `listApiKeys`, `createApiKey`, `deleteApiKey` |
|
|
491
|
-
| Billing | `getBilling`, `getBillingUsage`, `getBillingEntitlements`, `createBillingCheckout` |
|
|
1520
|
+
| API keys | Workspace: `listApiKeys`, `createApiKey`, `deleteApiKey`; organization: `listOrganizationApiKeys`, `createOrganizationApiKey`, `deleteOrganizationApiKey` |
|
|
1521
|
+
| Billing | `getBilling`, `getBillingUsage`, `getBillingEntitlements`, `createBillingCheckout`, `createBillingPortalSession` |
|
|
492
1522
|
|
|
493
|
-
`
|
|
1523
|
+
`listIntegrationDefinitions` returns safe Integration Definition metadata without any
|
|
494
1524
|
deployment OAuth client credentials. API Integrations are multi-instance:
|
|
495
1525
|
`installApiIntegration` may supply a
|
|
496
1526
|
stable `instanceKey`, display name, and exact Connection. The returned
|
|
@@ -499,14 +1529,14 @@ selected in one session without tool-name collision. Uninstall preview and
|
|
|
499
1529
|
uninstall both require the exact `instanceKey` and instance version; neither
|
|
500
1530
|
operation disconnects the underlying Connection.
|
|
501
1531
|
|
|
502
|
-
Adapter-owned Integration
|
|
503
|
-
`capabilityId` + `instanceKey` + `
|
|
504
|
-
`
|
|
1532
|
+
Adapter-owned Integration facets are listed and mutated under the exact
|
|
1533
|
+
`capabilityId` + `instanceKey` + `facetKey`. Generic primitive schemas use
|
|
1534
|
+
`configureIntegrationFacet`; lifecycle changes and removal require the
|
|
505
1535
|
binding's exact optimistic version plus a caller UUID idempotency key. Google
|
|
506
1536
|
Drive's nested folder/Shared Drive source schema has dedicated
|
|
507
|
-
`
|
|
1537
|
+
`browseGoogleDriveFacetSource` and `saveGoogleDriveFacetSource`
|
|
508
1538
|
methods. They verify provider metadata through that instance's exact Connection
|
|
509
|
-
and persist only a versioned
|
|
1539
|
+
and persist only a versioned facet binding—never provider credentials, page
|
|
510
1540
|
tokens, or selected-source configuration on the Connection.
|
|
511
1541
|
|
|
512
1542
|
### Protocol routes (deliberately not in the SDK)
|
|
@@ -521,15 +1551,21 @@ reconsider — they can change with their counterpart, not with the SDK.
|
|
|
521
1551
|
|
|
522
1552
|
## Compatibility
|
|
523
1553
|
|
|
524
|
-
Clients and servers are compatible within the same **major**
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
`/v1/config/client`.
|
|
1554
|
+
Clients and servers are compatible within the same **major** version of this
|
|
1555
|
+
SDK; evolution is additive within a major and both sides are tolerant readers.
|
|
1556
|
+
Official server builds expose `serverVersion` on `/healthz` and
|
|
1557
|
+
`/v1/config/client`. A route scheduled for removal answers with `Deprecation`
|
|
1558
|
+
and `Sunset` headers at least 90 days (and a major) ahead; the client reports
|
|
1559
|
+
each deprecated route once through the `onDeprecation` option (default: one
|
|
1560
|
+
`console.warn` per route; `false` silences it). Full policy:
|
|
1561
|
+
`docs/design/api-compatibility-policy.md`.
|
|
528
1562
|
|
|
529
1563
|
## Proxy through your own API
|
|
530
1564
|
|
|
531
|
-
|
|
532
|
-
|
|
1565
|
+
For the React conversation, use the packaged
|
|
1566
|
+
[`createSessionProxyHandler`](#embed-the-conversation-default). For a custom
|
|
1567
|
+
backend route, keep your organization API key on your server and re-emit the
|
|
1568
|
+
stream to your own browser clients. The re-emitted wire format is identical to OpenGeni's SSE
|
|
533
1569
|
stream, so the browser side can consume it with this same SDK (or a plain
|
|
534
1570
|
`EventSource`), including resume via `?after=` / `Last-Event-ID`:
|
|
535
1571
|
|
|
@@ -537,7 +1573,10 @@ stream, so the browser side can consume it with this same SDK (or a plain
|
|
|
537
1573
|
// Your server (Hono, Next.js route handler, Bun.serve, workers, ...):
|
|
538
1574
|
import { OpenGeniClient, proxySessionEventStream } from "@opengeni/sdk";
|
|
539
1575
|
|
|
540
|
-
const client = new OpenGeniClient({
|
|
1576
|
+
const client = new OpenGeniClient({
|
|
1577
|
+
baseUrl: process.env.OPENGENI_API_BASE_URL!,
|
|
1578
|
+
apiKey: process.env.OPENGENI_API_KEY!,
|
|
1579
|
+
});
|
|
541
1580
|
|
|
542
1581
|
export function GET(request: Request): Response {
|
|
543
1582
|
// authenticate *your* user, resolve their session id, then:
|
|
@@ -560,3 +1599,9 @@ packages. `test/contract-parity.test.ts` pins them to `@opengeni/contracts`,
|
|
|
560
1599
|
so contract drift fails the repo gate instead of shipping. `SessionEvent.type`
|
|
561
1600
|
is an open union: unknown event types from newer servers flow through instead
|
|
562
1601
|
of breaking older SDK consumers.
|
|
1602
|
+
|
|
1603
|
+
Hosts, test doubles, and same-origin proxies must import
|
|
1604
|
+
`OPENGENI_API_CONTRACT_REVISION` from `@opengeni/sdk` when constructing a client
|
|
1605
|
+
configuration or contract header. Do not copy its string value: the revision is
|
|
1606
|
+
an executable compatibility boundary, and `getClientConfig()` intentionally
|
|
1607
|
+
fails closed when server and SDK revisions differ.
|