@opengeni/sdk 6.1.0 → 6.2.2-canary.41203942106001
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 +974 -185
- 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/app-links.d.ts +99 -0
- package/dist/app-links.js +125 -0
- package/dist/app-links.js.map +1 -0
- package/dist/artifact-catalog.d.ts +13 -0
- package/dist/artifacts.js +9 -6
- package/dist/azure-live-transport.d.ts +32 -0
- package/dist/browser.d.ts +6 -2
- package/dist/browser.js +31 -10
- package/dist/chat/fold.d.ts +30 -1
- package/dist/chat/handler.d.ts +7 -5
- package/dist/chat/http.d.ts +4 -5
- package/dist/chat/index.d.ts +4 -2
- package/dist/chat/index.js +288 -351
- package/dist/chat/index.js.map +1 -1
- package/dist/chat/openai.d.ts +3 -3
- package/dist/chat/opengeni.d.ts +47 -11
- package/dist/chat/types.d.ts +83 -8
- package/dist/chat/vercel.d.ts +22 -4
- package/dist/chats.d.ts +8 -0
- package/dist/chunk-4B6PFXUO.js +47 -0
- package/dist/chunk-4B6PFXUO.js.map +1 -0
- package/dist/{chunk-4ZQQQBOH.js → chunk-7BUHPUFW.js} +2 -2
- package/dist/chunk-7L7D2AQH.js +17 -0
- package/dist/chunk-7L7D2AQH.js.map +1 -0
- package/dist/chunk-7RROJRPC.js +347 -0
- package/dist/chunk-7RROJRPC.js.map +1 -0
- package/dist/chunk-7Y47O2IY.js +210 -0
- package/dist/chunk-7Y47O2IY.js.map +1 -0
- package/dist/chunk-CB6D3GDF.js +366 -0
- package/dist/chunk-CB6D3GDF.js.map +1 -0
- package/dist/{chunk-7S2MZ65L.js → chunk-CWNNZPKL.js} +285 -41
- package/dist/chunk-CWNNZPKL.js.map +1 -0
- package/dist/chunk-ED3E7UIK.js +20 -0
- package/dist/chunk-ED3E7UIK.js.map +1 -0
- package/dist/chunk-EJDJORK2.js +561 -0
- package/dist/chunk-EJDJORK2.js.map +1 -0
- package/dist/chunk-HW3HBKAA.js +410 -0
- package/dist/chunk-HW3HBKAA.js.map +1 -0
- package/dist/chunk-KFTMQIVN.js +15 -0
- package/dist/chunk-KFTMQIVN.js.map +1 -0
- package/dist/chunk-KPNODZ56.js +1483 -0
- package/dist/chunk-KPNODZ56.js.map +1 -0
- package/dist/{chunk-QTBAMHEF.js → chunk-LEZERTRP.js} +93 -18
- package/dist/chunk-LEZERTRP.js.map +1 -0
- package/dist/chunk-QGEIL4TR.js +195 -0
- package/dist/chunk-QGEIL4TR.js.map +1 -0
- package/dist/chunk-R3X7MEQH.js +697 -0
- package/dist/chunk-R3X7MEQH.js.map +1 -0
- package/dist/chunk-S5CGKCMV.js +153 -0
- package/dist/chunk-S5CGKCMV.js.map +1 -0
- package/dist/{chunk-IZH5F7QC.js → chunk-SWROK57B.js} +25 -2
- package/dist/chunk-SWROK57B.js.map +1 -0
- package/dist/chunk-UM4ZXXJR.js +9 -0
- package/dist/chunk-UM4ZXXJR.js.map +1 -0
- package/dist/chunk-XCOAVV67.js +120 -0
- package/dist/chunk-XCOAVV67.js.map +1 -0
- package/dist/chunk-XZ4W25HT.js +156 -0
- package/dist/chunk-XZ4W25HT.js.map +1 -0
- package/dist/chunk-Y7DCU6MW.js +1990 -0
- package/dist/chunk-Y7DCU6MW.js.map +1 -0
- package/dist/chunk-YSQU5CHX.js +81 -0
- package/dist/chunk-YSQU5CHX.js.map +1 -0
- package/dist/chunk-YTHNS27C.js +283 -0
- package/dist/chunk-YTHNS27C.js.map +1 -0
- package/dist/{chunk-64S3VKJU.js → chunk-YYSOUVE4.js} +3665 -908
- package/dist/chunk-YYSOUVE4.js.map +1 -0
- package/dist/{chunk-CMEU67C4.js → chunk-ZGO4RCSW.js} +26 -3
- package/dist/chunk-ZGO4RCSW.js.map +1 -0
- package/dist/{chunk-VYCTL62C.js → chunk-ZHX765BU.js} +1 -1
- package/dist/chunk-ZHX765BU.js.map +1 -0
- package/dist/client.d.ts +1084 -114
- package/dist/codex-realtime-controller.d.ts +38 -1
- package/dist/codex-realtime-controller.js +4 -2
- package/dist/codex-realtime-lifecycle.d.ts +3 -0
- package/dist/codex-realtime-v3-wire.d.ts +3 -2
- package/dist/codex-realtime-v3.d.ts +14 -0
- package/dist/codex-realtime.d.ts +5 -1
- package/dist/connected-machine-output.d.ts +6 -0
- package/dist/core.d.ts +3 -1
- package/dist/core.js +25 -11
- package/dist/desktop-bridge.d.ts +811 -0
- package/dist/desktop-bridge.js +33 -0
- package/dist/document-authority-client.d.ts +8 -2
- package/dist/document-authority.js +8 -5
- package/dist/editable-artifacts/index.d.ts +1 -1
- package/dist/editable-artifacts/types.d.ts +12 -2
- package/dist/editable-artifacts.js +22 -6
- package/dist/editable-artifacts.js.map +1 -1
- package/dist/embedding-client.d.ts +82 -25
- package/dist/errors.d.ts +91 -1
- package/dist/external-vault-receiver.d.ts +203 -0
- package/dist/external-vault-receiver.js +564 -0
- package/dist/external-vault-receiver.js.map +1 -0
- package/dist/feedback.d.ts +68 -0
- package/dist/gateway-realtime-transport.d.ts +4 -0
- package/dist/gateway-realtime-transport.js +6 -1
- package/dist/index.d.ts +58 -19
- package/dist/index.js +1535 -258
- package/dist/index.js.map +1 -1
- package/dist/insights-usage.d.ts +37 -0
- package/dist/insights-usage.js +7 -0
- package/dist/insights-usage.js.map +1 -0
- package/dist/interaction.d.ts +311 -74
- package/dist/interaction.js +3 -2
- package/dist/knowledge.d.ts +5 -3
- package/dist/knowledge.js +12 -3
- package/dist/knowledge.js.map +1 -1
- package/dist/known-tools.d.ts +4 -0
- package/dist/learning-refusal.d.ts +6 -0
- package/dist/learning-refusal.js +39 -0
- package/dist/learning-refusal.js.map +1 -0
- package/dist/message-links.d.ts +76 -0
- package/dist/model-context.d.ts +9 -1
- package/dist/model-display.d.ts +2 -0
- package/dist/model-display.js +17 -0
- package/dist/model-display.js.map +1 -0
- package/dist/model-picker-order.d.ts +1 -1
- package/dist/model-picker-order.js +2 -0
- package/dist/model-picker-order.js.map +1 -1
- package/dist/office/dialog.d.ts +13 -0
- package/dist/office/executor.d.ts +24 -0
- package/dist/office/host.d.ts +67 -0
- package/dist/office/index.d.ts +8 -0
- package/dist/office/index.js +601 -0
- package/dist/office/index.js.map +1 -0
- package/dist/office/manifest.d.ts +31 -0
- package/dist/office/selection.d.ts +58 -0
- package/dist/office/watch.d.ts +10 -0
- package/dist/office/workbook-link.d.ts +59 -0
- package/dist/office/workbook.d.ts +27 -0
- package/dist/organization-integration-policy.d.ts +35 -0
- package/dist/organization-integration-policy.js +26 -0
- package/dist/organization-integration-policy.js.map +1 -0
- package/dist/prepared-mcp-setup.d.ts +5 -0
- package/dist/proxy.d.ts +7 -7
- package/dist/read-retry.d.ts +61 -0
- package/dist/realtime-transcript.d.ts +5 -0
- package/dist/realtime-websocket-transport.d.ts +3 -0
- package/dist/realtime-websocket-transport.js +8 -0
- package/dist/realtime-websocket-transport.js.map +1 -0
- package/dist/realtime.d.ts +12 -6
- package/dist/realtime.js +260 -6
- package/dist/realtime.js.map +1 -1
- package/dist/recording-live.d.ts +146 -0
- package/dist/recording-owner.d.ts +11 -0
- package/dist/recordings.d.ts +643 -0
- package/dist/recordings.js +2239 -0
- package/dist/recordings.js.map +1 -0
- package/dist/secret-use-approval.d.ts +1 -0
- package/dist/secret-use-approval.js +17 -0
- package/dist/secret-use-approval.js.map +1 -0
- 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 +56 -0
- package/dist/session-proxy-client.d.ts +17 -0
- package/dist/session-proxy.d.ts +373 -0
- package/dist/session-proxy.js +18 -0
- package/dist/session-proxy.js.map +1 -0
- package/dist/session-sequence.d.ts +20 -0
- package/dist/session-usage-limits.d.ts +10 -0
- package/dist/session-usage-limits.js +44 -0
- package/dist/session-usage-limits.js.map +1 -0
- package/dist/shared-catalog-loader.d.ts +34 -0
- package/dist/site-ai.d.ts +35 -0
- package/dist/site-bridge-host.d.ts +141 -0
- package/dist/site-bridge-host.js +348 -0
- package/dist/site-bridge-host.js.map +1 -0
- package/dist/site-browser-runtime.gen.d.ts +1 -1
- package/dist/site-data.d.ts +182 -0
- package/dist/site-document.js +10098 -1
- package/dist/site-document.js.map +1 -1
- package/dist/site-http.d.ts +2 -1
- package/dist/site-sql.d.ts +31 -0
- package/dist/site-tool-bridge.d.ts +70 -3
- package/dist/site.d.ts +198 -4
- package/dist/site.js +48 -6
- package/dist/skills.d.ts +9 -0
- package/dist/sse.d.ts +1 -1
- package/dist/stream.d.ts +37 -2
- package/dist/teams.d.ts +31 -0
- package/dist/teams.js +21 -0
- package/dist/teams.js.map +1 -0
- package/dist/tenant-workspaces.d.ts +81 -0
- package/dist/tenant-workspaces.js +8 -0
- package/dist/tenant-workspaces.js.map +1 -0
- package/dist/this-computer.d.ts +245 -0
- package/dist/this-computer.js +103 -0
- package/dist/this-computer.js.map +1 -0
- package/dist/timeline-order.d.ts +2 -0
- package/dist/timeline-order.js +170 -0
- package/dist/timeline-order.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-FSTY4FAY.js → tools-DUAU2VFG.js} +3 -3
- package/dist/tools-DUAU2VFG.js.map +1 -0
- package/dist/tools.d.ts +21 -1
- package/dist/transcription.d.ts +1 -1
- package/dist/types.d.ts +3381 -798
- 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/vault-types.d.ts +378 -0
- package/dist/vaults-api.d.ts +71 -0
- package/dist/workspace-artifacts.d.ts +60 -0
- package/dist/workspace-control-stream.d.ts +5 -0
- package/dist/workspace-integrations.d.ts +74 -0
- package/dist/workspace-integrations.js +67 -0
- package/dist/workspace-integrations.js.map +1 -0
- package/dist/workspace-live-stream.d.ts +15 -1
- package/package.json +115 -13
- 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/app-links.ts +246 -0
- package/src/artifact-catalog.ts +14 -0
- package/src/azure-live-transport.ts +286 -0
- package/src/browser.ts +30 -1
- package/src/chat/fold.ts +199 -34
- package/src/chat/handler.ts +10 -6
- package/src/chat/http.ts +4 -8
- package/src/chat/index.ts +12 -1
- package/src/chat/openai.ts +3 -3
- package/src/chat/opengeni.ts +315 -75
- package/src/chat/types.ts +84 -8
- package/src/chat/vercel.ts +42 -11
- package/src/chats.ts +16 -0
- package/src/client.ts +6107 -2383
- package/src/codex-realtime-controller.ts +324 -29
- package/src/codex-realtime-lifecycle.ts +22 -10
- package/src/codex-realtime-v3-wire.ts +4 -3
- package/src/codex-realtime-v3.ts +113 -22
- package/src/codex-realtime.ts +11 -3
- package/src/connected-machine-output.ts +17 -0
- package/src/core.ts +6 -0
- package/src/desktop-bridge.ts +890 -0
- package/src/document-authority-client.ts +39 -1
- package/src/editable-artifacts/controller.ts +36 -2
- package/src/editable-artifacts/index.ts +1 -0
- package/src/editable-artifacts/types.ts +29 -16
- package/src/embedding-client.ts +281 -122
- package/src/errors.ts +381 -7
- package/src/external-vault-receiver.ts +914 -0
- package/src/feedback.ts +68 -0
- package/src/gateway-realtime-transport.ts +7 -786
- package/src/index.ts +606 -63
- package/src/insights-usage.ts +68 -0
- package/src/interaction-revision-stream.ts +12 -76
- package/src/interaction.ts +500 -103
- package/src/knowledge.ts +23 -1
- package/src/known-tools.ts +279 -0
- package/src/learning-refusal.ts +9 -0
- package/src/message-links.ts +195 -0
- package/src/model-context.ts +31 -1
- package/src/model-display.ts +11 -0
- package/src/model-picker-order.ts +4 -0
- package/src/office/dialog.ts +50 -0
- package/src/office/executor.ts +115 -0
- package/src/office/host.ts +69 -0
- package/src/office/index.ts +41 -0
- package/src/office/manifest.ts +149 -0
- package/src/office/selection.ts +197 -0
- package/src/office/watch.ts +89 -0
- package/src/office/workbook-link.ts +177 -0
- package/src/office/workbook.ts +72 -0
- package/src/organization-integration-policy.ts +63 -0
- package/src/prepared-mcp-setup.ts +123 -0
- package/src/proxy.ts +7 -7
- package/src/read-retry.ts +208 -0
- package/src/realtime-transcript.ts +10 -0
- package/src/realtime-websocket-transport.ts +849 -0
- package/src/realtime.ts +35 -11
- package/src/recording-live.ts +916 -0
- package/src/recording-owner.ts +29 -0
- package/src/recordings.ts +2396 -0
- package/src/retained-artifacts.ts +20 -2
- package/src/secret-use-approval.ts +7 -0
- package/src/session-history-import.ts +84 -0
- package/src/session-list-entries.ts +1 -0
- package/src/session-message-search.ts +50 -0
- package/src/session-proxy-client.ts +42 -0
- package/src/session-proxy.ts +2187 -0
- package/src/session-sequence.ts +35 -0
- package/src/session-usage-limits.ts +73 -0
- package/src/shared-catalog-loader.ts +169 -0
- package/src/site-ai.ts +284 -0
- package/src/site-bridge-host.ts +518 -0
- package/src/site-browser-runtime.gen.ts +10098 -1
- package/src/site-data.ts +376 -0
- package/src/site-http.ts +12 -3
- package/src/site-sql.ts +153 -0
- package/src/site-tool-bridge.ts +510 -33
- package/src/site.ts +749 -15
- package/src/skills.ts +10 -0
- package/src/sse.ts +9 -3
- package/src/stream.ts +128 -6
- package/src/teams.ts +31 -0
- package/src/tenant-workspaces.ts +243 -0
- package/src/this-computer.ts +418 -0
- package/src/timeline-order.ts +9 -0
- package/src/tool-auth.ts +301 -0
- package/src/tool-display-metadata.ts +27 -0
- package/src/tools.ts +81 -25
- package/src/transcription.ts +5 -1
- package/src/types.ts +3933 -962
- package/src/usage-allowances.ts +189 -0
- package/src/vault-types.ts +448 -0
- package/src/vaults-api.ts +355 -0
- package/src/workspace-artifacts.ts +66 -0
- package/src/workspace-control-stream.ts +35 -70
- package/src/workspace-integrations.ts +403 -0
- package/src/workspace-live-stream.ts +58 -84
- package/dist/automations-client.d.ts +0 -17
- package/dist/automations.d.ts +0 -3
- package/dist/automations.js +0 -78
- package/dist/automations.js.map +0 -1
- package/dist/chunk-64S3VKJU.js.map +0 -1
- package/dist/chunk-7S2MZ65L.js.map +0 -1
- package/dist/chunk-CFSYTYMA.js +0 -639
- package/dist/chunk-CFSYTYMA.js.map +0 -1
- package/dist/chunk-CMEU67C4.js.map +0 -1
- package/dist/chunk-FMLDFJ2Q.js +0 -246
- package/dist/chunk-FMLDFJ2Q.js.map +0 -1
- package/dist/chunk-IZH5F7QC.js.map +0 -1
- package/dist/chunk-OO5LPTO7.js +0 -132
- package/dist/chunk-OO5LPTO7.js.map +0 -1
- package/dist/chunk-QTBAMHEF.js.map +0 -1
- package/dist/chunk-VDACQXJZ.js +0 -253
- package/dist/chunk-VDACQXJZ.js.map +0 -1
- package/dist/chunk-VYCTL62C.js.map +0 -1
- package/dist/chunk-YKZME56C.js +0 -657
- package/dist/chunk-YKZME56C.js.map +0 -1
- package/dist/pr-review-client.d.ts +0 -16
- package/dist/pr-review.d.ts +0 -4
- package/dist/pr-review.js +0 -62
- package/dist/pr-review.js.map +0 -1
- package/dist/site-document.test.d.ts +0 -1
- package/src/automations-client.ts +0 -118
- package/src/automations.ts +0 -15
- package/src/pr-review-client.ts +0 -92
- package/src/pr-review.ts +0 -17
- /package/dist/{chunk-4ZQQQBOH.js.map → chunk-7BUHPUFW.js.map} +0 -0
- /package/dist/{tools-FSTY4FAY.js.map → desktop-bridge.js.map} +0 -0
package/README.md
CHANGED
|
@@ -1,53 +1,6 @@
|
|
|
1
1
|
# @opengeni/sdk
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
On a product backend, `client.asUser(externalId, { source? })` returns a separate
|
|
6
|
-
client that sends the external actor assertion with the organization key. IDs
|
|
7
|
-
are opaque and case-sensitive; the default source is `default`. Keep the key and
|
|
8
|
-
this client server-side. Each request requires the intersection of the key's
|
|
9
|
-
permissions and the user's explicit workspace membership. API-key rotation does
|
|
10
|
-
not change the external identity.
|
|
11
|
-
|
|
12
|
-
Use the service client—not an `asUser` client—to call
|
|
13
|
-
`addExternalWorkspaceMember(workspaceId, { identity: { externalId, source? }, permissions })`
|
|
14
|
-
for explicit onboarding. It requires `members:manage`, cannot grant more than
|
|
15
|
-
the key permits, excludes Personal workspaces, and refuses to overwrite an
|
|
16
|
-
existing membership with different permissions. Ordinary `asUser` calls never
|
|
17
|
-
restore removed workspace membership or reactivate disabled identities.
|
|
18
|
-
|
|
19
|
-
Service `removeWorkspaceMember` supports external members through the existing
|
|
20
|
-
fenced workspace teardown. Account-wide `updateExternalIdentityMembership(
|
|
21
|
-
organizationId, organizationMembershipId, { kind, expectedAuthorizationRevision,
|
|
22
|
-
operationId, reason? })` requires explicit `account:admin` and supports suspend,
|
|
23
|
-
reactivate, and offboard. The membership ID is returned by onboarding; its initial
|
|
24
|
-
authorization revision is 1. Retain the returned revision for subsequent changes.
|
|
25
|
-
Reactivation restores admission, not revoked memberships or work. Offboarding
|
|
26
|
-
follows existing retention policy and cannot be reactivated through this API.
|
|
27
|
-
Native-user targets and `asUser` calls are rejected by this service endpoint.
|
|
28
|
-
|
|
29
|
-
An `asUser` client can discover/access its own provisioned Personal workspace
|
|
30
|
-
under the key's permission ceiling. Core private session operations retain
|
|
31
|
-
organization readiness/settings and explicit sharing acknowledgments; service
|
|
32
|
-
clients gain no Personal-workspace fallback.
|
|
33
|
-
|
|
34
|
-
Optional native-account delegation uses `beginIdentityLink`, authenticated native
|
|
35
|
-
`previewIdentityLink`/`confirmIdentityLink`, and explicit server-side
|
|
36
|
-
`asLinkedUser(externalId, { source?, linkId, expectedLinkRevision })`. Confirmation
|
|
37
|
-
requires the actual native login plus the one-time host challenge; an organization
|
|
38
|
-
key cannot confirm for the native user. `listIdentityLinks(workspaceId, cursor?)`
|
|
39
|
-
returns only the effective participant's links in that organization (50 per page).
|
|
40
|
-
`revokeIdentityLink` requires the observed revision. Linking never changes ordinary
|
|
41
|
-
`asUser`, merges histories or transfers credentials. Link-dependent accepted work
|
|
42
|
-
retains revocation checks across schedules and children. Short-lived inline MCP
|
|
43
|
-
credentials remain supported, and durable renewal stays opt-in.
|
|
44
|
-
|
|
45
|
-
Connect accepts an optional `installationTarget: { instanceKey, displayName,
|
|
46
|
-
expectedInstanceVersion? }`. Keep the exact observed version for an existing named
|
|
47
|
-
account. Setup freezes the target through callback and operation review; omitting
|
|
48
|
-
it creates an independent named account instead of overwriting a default instance.
|
|
49
|
-
|
|
50
|
-
Framework-agnostic TypeScript SDK for the OpenGeni public API: a typed client,
|
|
3
|
+
Framework-agnostic TypeScript SDK for the Opengeni public API: a typed client,
|
|
51
4
|
session lifecycle, and the streaming core — SSE event streaming with automatic
|
|
52
5
|
reconnect, resume-by-sequence, gap backfill, and duplicate suppression — plus
|
|
53
6
|
helpers for proxying the stream through your own API.
|
|
@@ -57,90 +10,358 @@ workspaces, Personal-workspace exclusion, tenant mapping, and external Skill
|
|
|
57
10
|
ownership—start with the canonical
|
|
58
11
|
[product integration guide](../../docs/product-integration.md).
|
|
59
12
|
|
|
60
|
-
|
|
61
|
-
|
|
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.
|
|
62
22
|
|
|
63
23
|
Browser clients may call the public API from any origin with an explicitly safe
|
|
64
24
|
bearer design, but an organization API key belongs on the product server.
|
|
65
25
|
Browser cookies are accepted cross-origin only from operator-configured trusted
|
|
66
26
|
origins; arbitrary embedding origins never receive credentialed CORS responses.
|
|
67
27
|
|
|
68
|
-
##
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
28
|
+
## Task usage limits
|
|
29
|
+
|
|
30
|
+
`createSession(workspaceId, { initialMessage, usageLimitMicros })` sets an
|
|
31
|
+
optional API-equivalent model-usage limit on a new root task;
|
|
32
|
+
`getSessionUsageLimit` and `setSessionUsageLimit` (compare-and-set on
|
|
33
|
+
`expectedVersion`, idempotent on `operationId`, `limitMicros: null` clears)
|
|
34
|
+
read and change it. Both writes verify the server's echo and throw when a
|
|
35
|
+
server that predates usage limits would otherwise run the task unlimited. A
|
|
36
|
+
refused message raises `OpenGeniSessionUsageLimitReachedError` (HTTP 402
|
|
37
|
+
`session_usage_limit_reached`), never the allowance error. The browser-safe
|
|
38
|
+
`@opengeni/sdk/session-usage-limits` subpath exports the free functions, the
|
|
39
|
+
refusal parser and neutral wording. The session proxy never forwards limits
|
|
40
|
+
from a browser; set them from your server. See
|
|
41
|
+
[task usage limits](../../docs/session-usage-limits.md).
|
|
42
|
+
|
|
43
|
+
## Allowance lifecycle recovery
|
|
44
|
+
|
|
45
|
+
`getWorkspaceAllowance(workspaceId)` still returns nullable configuration.
|
|
46
|
+
Use `getWorkspaceAllowanceState(workspaceId)` when you also need the lifecycle
|
|
47
|
+
version: `{ version: 0, config: null }` means never configured, while a positive
|
|
48
|
+
version with null configuration means cleared. Recreation uses that exact
|
|
49
|
+
version as `expectedVersion`.
|
|
50
|
+
|
|
51
|
+
`clearWorkspaceAllowance(workspaceId, { expectedVersion, operationId })` returns
|
|
52
|
+
`{ version }`. The optional operation ID makes a lost response recoverable:
|
|
53
|
+
reuse the same actor, operation ID and exact request. Replay rechecks current
|
|
54
|
+
authority and returns 409 after any later allowance lifecycle change. On 409,
|
|
55
|
+
read the current state and decide whether a new operation is intended; never
|
|
56
|
+
guess a CAS version. The SDK does not automatically retry mutations.
|
|
57
|
+
|
|
58
|
+
The same reads and administration are free functions on the
|
|
59
|
+
`@opengeni/sdk/usage-allowances` subpath for browser code that only has the
|
|
60
|
+
narrow `OpenGeniBrowserClient` (`getMyUsage(client, workspaceId)`,
|
|
61
|
+
`getAllUsage`, `setMemberAllowance`, ...); they call the same routes as the root methods.
|
|
62
|
+
|
|
63
|
+
State reads require the same budget-read authority as configuration reads.
|
|
64
|
+
Full usage additionally requires target workspace access; organization budget
|
|
65
|
+
authority alone does not expose the member roster. Agents cannot read or
|
|
66
|
+
change allowances.
|
|
67
|
+
|
|
68
|
+
## Embed the conversation (default)
|
|
69
|
+
|
|
70
|
+
Embedding Opengeni is one backend route plus one component, and you only speak
|
|
71
|
+
in your own ids. The browser renders `@opengeni/react`'s `OpenGeniChat` (or
|
|
72
|
+
`SessionConversation`); your server mounts `createSessionProxyHandler`, a
|
|
73
|
+
same-origin proxy that acts as your authenticated user:
|
|
74
74
|
|
|
75
75
|
```ts
|
|
76
|
-
|
|
76
|
+
// lib/opengeni.ts (server only: the organization API key never reaches the browser)
|
|
77
|
+
import { Opengeni } from "@opengeni/sdk/chat";
|
|
77
78
|
|
|
78
|
-
const og = new
|
|
79
|
-
|
|
80
|
-
organizationId: process.env.OPENGENI_ORGANIZATION_ID!,
|
|
81
|
-
// baseUrl defaults to https://app.opengeni.ai; source (default "app") labels your product.
|
|
82
|
-
});
|
|
79
|
+
export const og = new Opengeni({ apiKey: process.env.OPENGENI_API_KEY! });
|
|
80
|
+
```
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
82
|
+
```ts
|
|
83
|
+
// app/api/opengeni/[...path]/route.ts (any web-standard host works; see the adapters below)
|
|
84
|
+
import { createSessionProxyRoute } from "@opengeni/sdk/next";
|
|
85
|
+
import { og } from "@/lib/opengeni";
|
|
86
|
+
|
|
87
|
+
export const dynamic = "force-dynamic";
|
|
88
|
+
export const { GET, POST, PUT, PATCH, DELETE } = createSessionProxyRoute(og, {
|
|
89
|
+
resolve: async (request) => {
|
|
90
|
+
const me = await authenticate(request); // your existing session check
|
|
91
|
+
if (!me) return new Response("Unauthorized", { status: 401 });
|
|
92
|
+
return { user: me.id, tenant: me.orgId }; // or { user: me.id }
|
|
93
|
+
},
|
|
91
94
|
});
|
|
95
|
+
```
|
|
92
96
|
|
|
93
|
-
|
|
94
|
-
|
|
97
|
+
```tsx
|
|
98
|
+
// app/assistant/assistant.tsx (browser): no provider, client, or workspace id.
|
|
99
|
+
"use client";
|
|
100
|
+
import { OpenGeniChat } from "@opengeni/react";
|
|
101
|
+
import "@opengeni/react/compiled.css";
|
|
95
102
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (chunk.type === "pending") await chat.respond({ requestId: chunk.pending.requestId, decision: "approve" });
|
|
103
|
+
export function Assistant() {
|
|
104
|
+
return <OpenGeniChat baseUrl="/api/opengeni" />; // chat list + conversation
|
|
99
105
|
}
|
|
106
|
+
```
|
|
100
107
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
`baseUrl` makes the component create the browser client for your mount and use
|
|
109
|
+
the workspace the proxy resolved for the signed-in user (reported in the
|
|
110
|
+
proxy's client config). One conversation alone is
|
|
111
|
+
`<SessionConversation baseUrl="/api/opengeni" sessionId={id} />`. The
|
|
112
|
+
`<OpenGeniProvider client={client} workspaceId={workspaceId}>` form keeps
|
|
113
|
+
working, for example for several components or the headless hooks.
|
|
114
|
+
|
|
115
|
+
What `resolve` returns decides the workspace:
|
|
116
|
+
|
|
117
|
+
- `{ user, tenant }`: one workspace per tenant; chats are private per user by
|
|
118
|
+
default.
|
|
119
|
+
- `{ user }`: one workspace per user. It stays single-user: the SDK adds its
|
|
120
|
+
owner once, and Opengeni never admits anyone else to it.
|
|
121
|
+
- `{ user, workspaceId }`: bring your own workspace id.
|
|
122
|
+
|
|
123
|
+
Workspaces are created on first use and cached per `Opengeni` instance.
|
|
124
|
+
`og.workspaceId({ tenant })` and `og.workspaceId({ user })` return the same ids
|
|
125
|
+
the proxy uses. There is no onboarding step: the first request a user makes
|
|
126
|
+
adds them to a tenant (or your own) workspace with conversation permissions
|
|
127
|
+
(workspace read, session create/read/control, file upload/read, and attaching
|
|
128
|
+
your per-session MCP servers; no admin). That needs an organization API key
|
|
129
|
+
with `members:manage` (full access includes it) plus those permissions.
|
|
130
|
+
Opengeni never changes an existing membership, so narrowing or widening one
|
|
131
|
+
with `updateExternalWorkspaceMember` sticks. A user you remove from a tenant
|
|
132
|
+
workspace is added again on their next request, because your product decides
|
|
133
|
+
who its users are; stop resolving a user you no longer admit. A removal from a
|
|
134
|
+
per-user workspace sticks. `organizationId` is optional:
|
|
135
|
+
the facade reads it once from the key. Keep `user` and `tenant`
|
|
136
|
+
host-authenticated, never from the request body or path.
|
|
137
|
+
|
|
138
|
+
The proxy and facade accept `chats`. `"private"` (the default with a user)
|
|
139
|
+
uses private visibility, session-only agent reach and personal Knowledge;
|
|
140
|
+
`"shared"` uses workspace visibility, agent reach and Knowledge, so a tenant's
|
|
141
|
+
users see each other's chats. Without a user and with `chats` omitted, the
|
|
142
|
+
facade keeps workspace visibility, session-only agent reach and Knowledge
|
|
143
|
+
authoring off. The API wire values are `visibility: "private" | "workspace"`;
|
|
144
|
+
stored visibility is `user_private | workspace_shared`. Explicit hook/create
|
|
145
|
+
fields override defaults. Keep MCP URLs and credentials in the server's
|
|
146
|
+
`createSession` hook; the browser cannot choose them through the proxy.
|
|
147
|
+
|
|
148
|
+
You can also pass a plain `OpenGeniClient` (organization key) instead of the
|
|
149
|
+
facade; then `resolve` must return `{ user, workspaceId }`. The explicit
|
|
150
|
+
membership APIs (`ensureWorkspace`, `addExternalWorkspaceMember`,
|
|
151
|
+
`updateExternalWorkspaceMember`, `cancelExternalWorkspaceMemberGrant`) keep
|
|
152
|
+
working for products that manage access themselves. Legacy
|
|
153
|
+
`chats: "isolated"` with `{ tenant, user }` still provisions a separate
|
|
154
|
+
workspace per user and tenant. Per-user workspaces add their owner with a
|
|
155
|
+
stable onboarding key, so a retry never restores a revoked grant;
|
|
156
|
+
`memberPermissions` replaces that owner's permissions on first onboarding
|
|
157
|
+
only. Set `memberPermissions` for tenant workspaces too when your users need
|
|
158
|
+
another role than Opengeni's first-use defaults: the facade and the proxy then
|
|
159
|
+
add each signed-in user with exactly those permissions before their first
|
|
160
|
+
request, with the same stable onboarding key, and never change an existing
|
|
161
|
+
grant. The standalone resolver is on the server-only
|
|
162
|
+
`@opengeni/sdk/tenant-workspaces` subpath.
|
|
163
|
+
|
|
164
|
+
The facade also accepts `agent` (identity, capabilities, instructions, renderer)
|
|
165
|
+
and defaults its renderer to `"markdown"`. Current servers always admit agent
|
|
166
|
+
configuration. For older servers that still gate it, only an implicit renderer
|
|
167
|
+
retries once without `agent` on `422 agent_config_not_enabled`, and that refusal
|
|
168
|
+
is cached per facade instance. Explicit agent settings are never stripped: the
|
|
169
|
+
422 names `OPENGENI_AGENT_CONFIG_ADMISSION_ENABLED` for that older server's
|
|
170
|
+
operator. The private default with a user now turns personal Knowledge
|
|
171
|
+
on; pass `memory: false` to keep authoring off.
|
|
172
|
+
Missing organization private-session enablement raises `OpenGeniSetupError`,
|
|
173
|
+
with owner/admin API, SDK and web-app instructions; the proxy preserves that
|
|
174
|
+
actionable error for browser clients.
|
|
175
|
+
|
|
176
|
+
Framework adapters are thin wrappers over the same web-standard handler (they
|
|
177
|
+
also accept `createChatHandler` or any `(Request) => Promise<Response>`):
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
// Next.js App Router: app/api/opengeni/[...path]/route.ts
|
|
181
|
+
import { createSessionProxyRoute } from "@opengeni/sdk/next";
|
|
182
|
+
export const dynamic = "force-dynamic";
|
|
183
|
+
export const { GET, POST, PUT, PATCH, DELETE } = createSessionProxyRoute(og, { resolve });
|
|
184
|
+
|
|
185
|
+
// Express / Connect / node:http (mount before body parsers, or they are re-serialized)
|
|
186
|
+
import { toNodeMiddleware } from "@opengeni/sdk/express";
|
|
187
|
+
app.use("/api/opengeni", toNodeMiddleware(handler));
|
|
188
|
+
|
|
189
|
+
// Hono
|
|
190
|
+
import { toHonoHandler } from "@opengeni/sdk/hono";
|
|
191
|
+
app.all("/api/opengeni/*", toHonoHandler(handler));
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The Next adapter passes the full URL, including any `basePath`; the Node
|
|
195
|
+
middleware rebuilds it from `originalUrl`, streams bodies and SSE, and aborts
|
|
196
|
+
the request when the client disconnects.
|
|
197
|
+
|
|
198
|
+
The proxy is a relay: it passes Opengeni's own routes through as the resolved
|
|
199
|
+
user, so the browser can use everything Opengeni lets that user do in that
|
|
200
|
+
workspace (chats and their history, queue, goals, approvals and human input,
|
|
201
|
+
files, artifacts and Sites, voice, schedules, Knowledge) with the same
|
|
202
|
+
requests the Opengeni web app makes. What the user can do is decided by
|
|
203
|
+
Opengeni's permissions, not by a list in the proxy; what the user sees is
|
|
204
|
+
decided by your UI (component props and presets). The chat list
|
|
205
|
+
(`SessionList` / `OpenGeniChat`) shows every root chat Opengeni lets the user
|
|
206
|
+
read; `listProps={{ mineOnly: true }}` narrows it to the user's own chats.
|
|
207
|
+
`listProps={{ archive: false }}` and `newChat={false}` hide "Hide for me" and
|
|
208
|
+
"New chat". That personal archive only hides a chat from one member's list.
|
|
209
|
+
The deployment's idle-session archive is separate: it makes long-idle sessions
|
|
210
|
+
read-only and is reported on `session.retention`. Pass `keepLive: true` to
|
|
211
|
+
`createSession`, or call
|
|
212
|
+
`updateSessionRetention(workspaceId, sessionId, { keepLive: true })`, to keep a
|
|
213
|
+
persistent agent's session out of it.
|
|
214
|
+
|
|
215
|
+
Composer uploads are refused for anonymous visitors: return `visitor: true`
|
|
216
|
+
from `resolve` for a user your product has not signed in (for example a
|
|
217
|
+
website visitor keyed by a cookie), and the proxy reports file uploads off and
|
|
218
|
+
refuses the upload routes unless `visitorUploads: true`. Agent-produced media
|
|
219
|
+
still loads.
|
|
220
|
+
|
|
221
|
+
Live voice (speech-to-speech) and voice input are Opengeni routes like any
|
|
222
|
+
other. The stock conversation shows its voice button only when you opt in with
|
|
223
|
+
the component's `realtimeVoice` prop, and its microphone unless `voiceInput` is
|
|
224
|
+
`false`. Opengeni still checks the user's `sessions:control` permission, binds
|
|
225
|
+
a call to that user and browser, runs spoken requests as steers of the same
|
|
226
|
+
chat, and meters deployment-funded voice against your credits.
|
|
227
|
+
`authorizeSession` runs on every call route, and `beforeForwardMessage` sees
|
|
228
|
+
`delivery: "realtime"` when a call starts (refuse it, or return
|
|
229
|
+
`mcpCredentialUpdates`; the proxy also refreshes the `toolServer` token) and
|
|
230
|
+
before transcripts and spoken requests are saved (refuse them, or add
|
|
231
|
+
`modelContext`, which must be the same when a request is retried).
|
|
232
|
+
|
|
233
|
+
`listSessionPage` includes personal pinned details by default. A caller with a
|
|
234
|
+
separate `pinsOnly: true` read can pass `includePinned: false` on ordinary pages
|
|
235
|
+
to avoid repeatedly loading those details. Ordinary pages still exclude pinned
|
|
236
|
+
rows; `pinsOnly: true` and `includePinned: false` cannot be combined. Older API
|
|
237
|
+
versions may ignore `includePinned` and return the default pin projection.
|
|
238
|
+
|
|
239
|
+
For navigation lists, `listSessionSummaryPage` uses the same filters, ordering,
|
|
240
|
+
pin rules and cursors, returning `SessionListEntry` records. Entries include
|
|
241
|
+
display/rename titles, creator, lifecycle, personal state, hierarchy counts and
|
|
242
|
+
the model and reasoning effort shown with a row; prompts and the rest of the
|
|
243
|
+
execution configuration remain on `getSession`. Older APIs return
|
|
244
|
+
full pages that the SDK projects locally. Unknown filters/cursors still fail
|
|
245
|
+
explicitly rather than returning an unfiltered result.
|
|
246
|
+
|
|
247
|
+
Every request calls `resolve`, then runs through `asUser(user, { source })`
|
|
248
|
+
(there is no service-authority fallback) against exactly the resolved
|
|
249
|
+
workspace:
|
|
250
|
+
|
|
251
|
+
- **Inside the workspace** (`/v1/workspaces/{workspaceId}/...`), every route
|
|
252
|
+
and method is forwarded as the user, with its query string, streaming body,
|
|
253
|
+
and the request headers Opengeni reads (`Accept`, `Content-Type`, `Range`,
|
|
254
|
+
`Last-Event-ID`, conditional headers, `x-opengeni-session-id`, correlation
|
|
255
|
+
and chunked-upload headers). Responses keep Opengeni's status, error
|
|
256
|
+
envelope, and content headers; SSE streams get heartbeats. A path naming
|
|
257
|
+
another workspace is a 403.
|
|
258
|
+
- **Outside it**, only the bootstrap reads answer, for this workspace alone:
|
|
259
|
+
`GET /v1/workspaces` (just the resolved workspace), `GET /v1/access/me` (the
|
|
260
|
+
user's effective grant here, never their other workspaces or the key's
|
|
261
|
+
authority), and `GET /v1/config/client`, plus the user's own inbox:
|
|
262
|
+
`GET /v1/inbox` and `PATCH /v1/inbox/items/{id}` limited to this workspace's
|
|
263
|
+
items, and `GET`/`PUT /v1/inbox/settings`. Everything else is a 404. Your
|
|
264
|
+
users' questions, approvals, paused goals and agent notifications reach that
|
|
265
|
+
inbox like an Opengeni member's (the stock app shows it), so you do not need
|
|
266
|
+
to build notifications from session events.
|
|
267
|
+
- **What narrows it:** an `asUser` request holds only the permissions both the
|
|
268
|
+
organization key and the user's membership hold, so a key or member role
|
|
269
|
+
without `files:write`, `schedules:*` or `sessions:control` cannot upload,
|
|
270
|
+
schedule or pause. `refuse(route, context)` keeps routes closed that
|
|
271
|
+
Opengeni would allow (return `true` for a 404 or your own `Response`;
|
|
272
|
+
`route` is `{ method, segments, sessionId }` with the segments after
|
|
273
|
+
`/v1/workspaces/{workspaceId}`). `authorizeSession(sessionId, context)` adds
|
|
274
|
+
a product-level check for every `sessions/{id}/...` route and every request
|
|
275
|
+
naming a chat in `x-opengeni-session-id`.
|
|
276
|
+
- **Server-owned chat setup:** with `createSession`, the browser may send only
|
|
277
|
+
`initialMessage`, `idempotencyKey`, attached files, explicit model choices,
|
|
278
|
+
and a few UI fields; your hook returns the full request (tools, MCP servers,
|
|
279
|
+
Skills, instructions, model policy), and the proxy refuses the routes that
|
|
280
|
+
would change that setup afterwards (a chat's agent, tool policy, Skills,
|
|
281
|
+
variable sets, usage limit, visibility, active sandbox and Codex account,
|
|
282
|
+
forks, and `chat/completions`). Without the hook, the browser creates chats
|
|
283
|
+
with Opengeni's own request, limited by the user's permissions, with `chats`
|
|
284
|
+
defaults filling what it leaves out. MCP credential rotation and archived
|
|
285
|
+
history import (`session-imports`) from the browser are always refused.
|
|
286
|
+
- **Messages** (send, steer, draft submit, realtime) pass through
|
|
287
|
+
`beforeForwardMessage`; `modelSelection: false` strips per-message model
|
|
288
|
+
choices, and bodies are capped by `maxBodyBytes` (1 MiB).
|
|
289
|
+
- **Mutations:** without `authorizeMutation`, cross-site (`Sec-Fetch-Site`)
|
|
290
|
+
requests and the content types a cross-site form can send (`text/plain`,
|
|
291
|
+
`application/x-www-form-urlencoded`) are refused, so cookie-authenticated
|
|
292
|
+
hosts should pass their CSRF check.
|
|
293
|
+
|
|
294
|
+
The client config the proxy serves is Opengeni's for this workspace and user,
|
|
295
|
+
plus the resolved `workspaceId`, the SDK's own `apiContractRevision`, the
|
|
296
|
+
artifact viewer's `artifacts` capability (the editor's live socket and this
|
|
297
|
+
user's cache partition), file uploads off for visitors, and `modelSelection`
|
|
298
|
+
when you set it. Task usage limits stay server-side.
|
|
299
|
+
|
|
300
|
+
### Your own tools, as the signed-in user
|
|
301
|
+
|
|
302
|
+
`toolServer` attaches your product's MCP endpoint to every session created
|
|
303
|
+
through the proxy, with a short-lived token for the user `resolve`
|
|
304
|
+
authenticated. The proxy refreshes it on every send, steer, submit, approval,
|
|
305
|
+
and human-input answer. Your endpoint, built with any MCP library, verifies it:
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
createSessionProxyHandler(og, {
|
|
309
|
+
resolve,
|
|
310
|
+
createSession,
|
|
311
|
+
toolServer: {
|
|
312
|
+
url: "https://app.example.com/api/mcp", // public HTTPS (tunnel locally)
|
|
313
|
+
approvals: { ask: ["rename_post"] }, // the user approves each call first
|
|
111
314
|
},
|
|
112
|
-
// format: "vercel" | "openai-chat" | "openai-responses" (default "native");
|
|
113
|
-
// a request may override it with the x-opengeni-chat-format header.
|
|
114
315
|
});
|
|
115
|
-
|
|
116
|
-
|
|
316
|
+
|
|
317
|
+
// In the MCP route (Web Request, or an Express/Node request):
|
|
318
|
+
import { ToolRequestError, verifyToolRequest } from "@opengeni/sdk/tool-auth";
|
|
319
|
+
const { user, tenant, workspaceId } = await verifyToolRequest(request); // or ToolRequestError (401)
|
|
117
320
|
```
|
|
118
321
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
the
|
|
322
|
+
Scope every tool to the verified `user` and `tenant`; never trust ids the model
|
|
323
|
+
sends; list write tools in `approvals.ask`. Tools act as the chat's creator, also
|
|
324
|
+
in shared chats. Members need `mcp_servers:attach`. Tokens are HS256 JWTs signed
|
|
325
|
+
with a key derived from `OPENGENI_API_KEY` (or the same `secret` on both sides),
|
|
326
|
+
bound to the full tool URL by `aud` (set `OPENGENI_TOOL_SERVER_URL` to configure
|
|
327
|
+
both sides at once), and valid for `ttlSeconds` (default 24 hours). Non-Node
|
|
328
|
+
verifiers use the hex key from `deriveToolTokenKey()`, never the organization key;
|
|
329
|
+
the format is documented in `src/tool-auth.ts`. See
|
|
330
|
+
`examples/tool-server` for a runnable Express version.
|
|
127
331
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
332
|
+
`beforeForwardMessage(message, context)` runs before every forwarded user
|
|
333
|
+
message (send, steer, composer submit, and a browser-started create) and may
|
|
334
|
+
return server-owned additions, or a `Response` to refuse the message:
|
|
131
335
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
336
|
+
```ts
|
|
337
|
+
createSessionProxyHandler(og, {
|
|
338
|
+
resolve,
|
|
339
|
+
beforeForwardMessage: async ({ sessionId, delivery }, { user }) => ({
|
|
340
|
+
// Model-visible, placed before any context the browser sent.
|
|
341
|
+
modelContext: `Page ${currentPage(user)} · ${timeZone(user)} · ${today()}`,
|
|
342
|
+
// Header-only rotation of MCP servers already attached to the session,
|
|
343
|
+
// applied atomically as the message is accepted (ignored on create).
|
|
344
|
+
mcpCredentialUpdates: [
|
|
345
|
+
{ id: "acme", headers: { Authorization: `Bearer ${await mintUserToken(user)}` } },
|
|
346
|
+
],
|
|
347
|
+
}),
|
|
348
|
+
});
|
|
349
|
+
```
|
|
138
350
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
351
|
+
This is the per-user tool token pattern: create the session with
|
|
352
|
+
`mcpServers: [{ id: "acme", url, headers }]` (and `tools: [{ kind: "mcp", id:
|
|
353
|
+
"acme" }]`, which the acting user needs `mcp_servers:attach` to attach), then
|
|
354
|
+
hand the MCP server a fresh short-lived bearer on every message. The browser
|
|
355
|
+
still cannot send `mcpCredentialUpdates` itself. With `modelSelection: false`
|
|
356
|
+
the proxy reports `modelSelection: false` in the client config, and
|
|
357
|
+
`SessionConversation` hides its model picker.
|
|
358
|
+
|
|
359
|
+
Opengeni adds each user to the workspace on their first request (see
|
|
360
|
+
[External users](#external-users-asuser)), with `workspace:read`,
|
|
361
|
+
`sessions:create`, `sessions:read`, `sessions:control`, `files:upload`,
|
|
362
|
+
`files:read`, and `mcp_servers:attach`. Pass the facade from `@opengeni/sdk/chat`
|
|
363
|
+
instead of a client to resolve `{ tenant, user }` or `{ user }` to a workspace
|
|
364
|
+
created on first use.
|
|
144
365
|
|
|
145
366
|
## Quick start
|
|
146
367
|
|
|
@@ -148,7 +369,7 @@ forks, and realtime voice.
|
|
|
148
369
|
import { OpenGeniClient } from "@opengeni/sdk";
|
|
149
370
|
|
|
150
371
|
const client = new OpenGeniClient({
|
|
151
|
-
baseUrl:
|
|
372
|
+
baseUrl: process.env.OPENGENI_API_BASE_URL!,
|
|
152
373
|
apiKey: process.env.OPENGENI_API_KEY!,
|
|
153
374
|
});
|
|
154
375
|
|
|
@@ -169,14 +390,14 @@ for await (const event of client.streamEvents(workspaceId, session.id)) {
|
|
|
169
390
|
## Organization-backed product setup
|
|
170
391
|
|
|
171
392
|
A multi-tenant product should hold one organization API key on its backend and
|
|
172
|
-
idempotently map each product tenant to an
|
|
393
|
+
idempotently map each product tenant to an Opengeni organization workspace. The
|
|
173
394
|
wire kind for an organization workspace is `"shared"`; Personal workspaces are
|
|
174
395
|
excluded.
|
|
175
396
|
|
|
176
397
|
```ts
|
|
177
398
|
const client = new OpenGeniClient({
|
|
178
399
|
baseUrl: process.env.OPENGENI_API_BASE_URL!,
|
|
179
|
-
apiKey: process.env.
|
|
400
|
+
apiKey: process.env.OPENGENI_API_KEY!,
|
|
180
401
|
});
|
|
181
402
|
|
|
182
403
|
const organizationId = process.env.OPENGENI_ORGANIZATION_ID!;
|
|
@@ -195,17 +416,40 @@ if (workspace.kind !== "shared") {
|
|
|
195
416
|
The response is `{ workspace, created }`. Replays return the original nested
|
|
196
417
|
workspace with `created: false`. For an organization key, `getAccessContext()`
|
|
197
418
|
does not enumerate every workspace grant; call `listWorkspaces()` for the
|
|
198
|
-
|
|
419
|
+
organization-workspace inventory allowed by the key's live scope.
|
|
199
420
|
|
|
200
421
|
Organization key administration uses `listOrganizationApiKeys`,
|
|
201
|
-
`createOrganizationApiKey`,
|
|
422
|
+
`getOrganizationApiKey`, `createOrganizationApiKey`, `updateOrganizationApiKey`,
|
|
423
|
+
and `deleteOrganizationApiKey`. GET detail and PATCH return the raw `ApiKey`;
|
|
424
|
+
creation returns `{ apiKey, token }`. The key token from a
|
|
202
425
|
create response is shown once and must be stored in the backend's secret
|
|
203
426
|
manager. `createOrganizationApiKey(organizationId, { name, access: "read" })`
|
|
204
427
|
mints a read-only master key: it inventories shared workspaces and reads their
|
|
205
428
|
sessions, events, and files, but cannot create sessions, send messages, or mint
|
|
206
|
-
keys
|
|
429
|
+
keys. This legacy `access: "read"` tier is distinct from the broader explicit
|
|
430
|
+
`read_only` preset, which includes every read/list/view/search permission
|
|
431
|
+
except the secret-value read `secrets:read`.
|
|
432
|
+
|
|
433
|
+
Create or edit with `policy: { preset, permissions, workspaceScope }` for exact
|
|
434
|
+
grants. `OrganizationAccessPreset` is `"read_only" | "full" | "custom"`;
|
|
435
|
+
the label never adds permissions and the server recomputes it from the selected
|
|
436
|
+
set. Explicit `full` selects every canonical non-deprecated permission; `custom`
|
|
437
|
+
has no implicit grants or scope. Even `workspace:admin` grants no wildcard under
|
|
438
|
+
an explicit policy. Scope is `{ kind: "all" }` (current and future shared
|
|
439
|
+
workspaces) or `{ kind: "selected", workspaceIds }` (up to 500 unique same-org
|
|
440
|
+
shared-workspace IDs; empty reaches none). Personal workspaces are always excluded.
|
|
441
|
+
|
|
442
|
+
`updateOrganizationApiKey(organizationId, apiKeyId, { name?, description?, policy? })`
|
|
443
|
+
requires at least one change; `description: null` clears it. Metadata-only edits
|
|
444
|
+
preserve legacy stored permissions and wildcard semantics. A policy edit replaces
|
|
445
|
+
the policy, transitions legacy keys to explicit semantics, and applies narrowing
|
|
446
|
+
on the next request without rotating the token. Omitting policy at creation
|
|
447
|
+
retains legacy `access`/`preset` inputs and the full legacy default.
|
|
448
|
+
See [the canonical policy guide](../../docs/product-integration.md#explicit-organization-key-policies).
|
|
449
|
+
|
|
450
|
+
Keys with session-read authority can call
|
|
207
451
|
`listOrganizationSessions(organizationId, { limit, cursor, scopeSubjectId, status })`
|
|
208
|
-
for one page of sessions across
|
|
452
|
+
for one page of sessions across authorized shared workspaces (each row carries its
|
|
209
453
|
`workspaceId`; private sessions and Personal workspaces never appear), or
|
|
210
454
|
`iterateOrganizationSessions` to follow `nextCursor` to the end.
|
|
211
455
|
|
|
@@ -215,10 +459,10 @@ session. There is no organization-wide Skill registry or Skill inheritance in
|
|
|
215
459
|
this integration contract. See the canonical guide for the complete route map,
|
|
216
460
|
security boundary, and delivery checklist.
|
|
217
461
|
|
|
218
|
-
Installed
|
|
219
|
-
|
|
462
|
+
Installed Skills can be workspace-managed or marked `session_selected`.
|
|
463
|
+
Session-selected guidance is exposed to no agent
|
|
220
464
|
until `createSession` names the reviewed immutable component in
|
|
221
|
-
`installedSkillIds`.
|
|
465
|
+
`installedSkillIds`. Opengeni copies that exact artifact into the new session,
|
|
222
466
|
so omitting the field from customer-facing creates is a real contamination
|
|
223
467
|
boundary rather than a prompt convention.
|
|
224
468
|
The ordered selection is part of keyed-create identity: reusing an
|
|
@@ -242,11 +486,200 @@ root and `@opengeni/sdk/core` clients, or through the focused
|
|
|
242
486
|
import { OpenGeniDocumentAuthorityClient } from "@opengeni/sdk/document-authority";
|
|
243
487
|
|
|
244
488
|
const operatorClient = new OpenGeniDocumentAuthorityClient({
|
|
245
|
-
baseUrl:
|
|
489
|
+
baseUrl: process.env.OPENGENI_API_BASE_URL!,
|
|
246
490
|
apiKey: process.env.OPENGENI_API_KEY!,
|
|
247
491
|
});
|
|
248
492
|
```
|
|
249
493
|
|
|
494
|
+
## External users (`asUser`)
|
|
495
|
+
|
|
496
|
+
On a product backend, `client.asUser(externalId, { source? })` returns a separate
|
|
497
|
+
client that sends the external actor assertion with the organization key. IDs
|
|
498
|
+
are opaque and case-sensitive; the default source is `default`. Keep the key and
|
|
499
|
+
this client server-side. Each request requires the intersection of the key's
|
|
500
|
+
permissions and the user's workspace membership. API-key rotation does not
|
|
501
|
+
change the external identity.
|
|
502
|
+
|
|
503
|
+
Membership is automatic: when an `asUser` request reaches a shared workspace of
|
|
504
|
+
the key's own organization and the user has no membership there, Opengeni adds
|
|
505
|
+
one with conversation permissions (workspace read, session create/read/control,
|
|
506
|
+
file upload/read, `mcp_servers:attach`) and continues the request. This needs
|
|
507
|
+
the same authority as explicit onboarding: the key holds `members:manage` (or a
|
|
508
|
+
legacy `workspace:admin`) and every one of those permissions, with the
|
|
509
|
+
workspace in its scope; otherwise the request is a `403` as before. It never
|
|
510
|
+
applies to linked native identities, Personal workspaces, SDK per-user
|
|
511
|
+
workspaces (`opengeni-sdk:user-isolation:*` external source), other
|
|
512
|
+
organizations, requests that need a permission outside those defaults, or any
|
|
513
|
+
non-key caller, and it never changes an existing membership. The identity is
|
|
514
|
+
re-checked as active under the same lock, and the grant writes the same
|
|
515
|
+
receipt and lifecycle event as explicit onboarding, attributed to the key. A
|
|
516
|
+
user removed from a shared workspace is added again on their next request; a
|
|
517
|
+
suspended or offboarded identity stays refused.
|
|
518
|
+
|
|
519
|
+
Use the service client—not an `asUser` client—to call
|
|
520
|
+
`addExternalWorkspaceMember(workspaceId, { identity: { externalId, source? }, permissions })`
|
|
521
|
+
to choose permissions up front. It requires `members:manage`, cannot grant more
|
|
522
|
+
than the key permits, excludes Personal workspaces, and refuses to overwrite an
|
|
523
|
+
existing membership with different permissions (`409`, also when automatic
|
|
524
|
+
membership already created it); change an existing member's permissions with the keyed
|
|
525
|
+
`updateExternalWorkspaceMember(organizationId, workspaceId, organizationMembershipId,
|
|
526
|
+
{ operationId, permissions })`, which never tears down work. `asUser` calls never
|
|
527
|
+
reactivate disabled identities.
|
|
528
|
+
|
|
529
|
+
Service `removeWorkspaceMember` supports external members through the existing
|
|
530
|
+
fenced workspace teardown. Account-wide `updateExternalIdentityMembership(
|
|
531
|
+
organizationId, organizationMembershipId, { kind, expectedAuthorizationRevision,
|
|
532
|
+
operationId, reason? })` requires explicit `account:admin` and supports suspend,
|
|
533
|
+
reactivate, and offboard. The membership ID is returned by onboarding; its initial
|
|
534
|
+
authorization revision is 1. Retain the returned revision for subsequent changes.
|
|
535
|
+
Reactivation restores admission, not revoked memberships or work. Offboarding
|
|
536
|
+
follows existing retention policy and cannot be reactivated through this API.
|
|
537
|
+
Native-user targets and `asUser` calls are rejected by this service endpoint.
|
|
538
|
+
|
|
539
|
+
An `asUser` client can discover/access its own provisioned Personal workspace
|
|
540
|
+
under the key's permission ceiling. Core private session operations retain
|
|
541
|
+
organization readiness/settings and explicit sharing acknowledgments; service
|
|
542
|
+
clients gain no Personal-workspace fallback.
|
|
543
|
+
|
|
544
|
+
Optional native-account delegation uses `beginIdentityLink`, authenticated native
|
|
545
|
+
`previewIdentityLink`/`confirmIdentityLink`, and explicit server-side
|
|
546
|
+
`asLinkedUser(externalId, { source?, linkId, expectedLinkRevision })`. Confirmation
|
|
547
|
+
requires the actual native login plus the one-time host challenge; an organization
|
|
548
|
+
key cannot confirm for the native user. `listIdentityLinks(workspaceId, cursor?)`
|
|
549
|
+
returns only the effective participant's links in that organization (50 per page).
|
|
550
|
+
`revokeIdentityLink` requires the observed revision. Linking never changes ordinary
|
|
551
|
+
`asUser`, merges histories or transfers credentials. Link-dependent accepted work
|
|
552
|
+
retains revocation checks across schedules and children. Short-lived inline MCP
|
|
553
|
+
credentials remain supported, and durable renewal stays opt-in.
|
|
554
|
+
|
|
555
|
+
Connect accepts an optional `installationTarget: { instanceKey, displayName,
|
|
556
|
+
expectedInstanceVersion? }`. Keep the exact observed version for an existing named
|
|
557
|
+
account. Setup freezes the target through callback and operation review; omitting
|
|
558
|
+
it creates an independent named account instead of overwriting a default instance.
|
|
559
|
+
|
|
560
|
+
## Chat facade fallback (`@opengeni/sdk/chat`)
|
|
561
|
+
|
|
562
|
+
Use this only when your product already has its own chat UI speaking Vercel
|
|
563
|
+
`useChat` or an OpenAI-shaped protocol and you want a compatible drop-in
|
|
564
|
+
backend, or for server-side bots (`og.chat(...).send()`). It is a text-only
|
|
565
|
+
projection: tool outputs are dropped (the Vercel adapter emits no tool parts
|
|
566
|
+
by default, and only `output: { status }` with `toolParts: true`), there are no
|
|
567
|
+
files, attachments, artifacts, or images,
|
|
568
|
+
no goals/queue/steer UI, and reopening restores only a text snapshot. For
|
|
569
|
+
anything else, use [the default conversation embed](#embed-the-conversation-default).
|
|
570
|
+
|
|
571
|
+
One option object per conversation, one server handler for your endpoint.
|
|
572
|
+
Tenants map to organization workspaces, conversations map to deterministic
|
|
573
|
+
sessions, and the organization API key never leaves your server.
|
|
574
|
+
|
|
575
|
+
```ts
|
|
576
|
+
import { Opengeni, createChatHandler } from "@opengeni/sdk/chat";
|
|
577
|
+
|
|
578
|
+
const og = new Opengeni({
|
|
579
|
+
baseUrl: process.env.OPENGENI_API_BASE_URL!, // omitted = production https://app.opengeni.ai
|
|
580
|
+
apiKey: process.env.OPENGENI_API_KEY!, // the organization id is derived from the key
|
|
581
|
+
source: "acme-app", // labels your product's tenants and users (default "app")
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
const chat = await og.chat({
|
|
585
|
+
tenant: "acme", // one workspace per customer, created on first use; omit for one per user
|
|
586
|
+
user: "u_42", // authenticated product user; added to the workspace on first use
|
|
587
|
+
conversation: "c_9", // stable conversation id, independent of the acting user
|
|
588
|
+
agentAccess: "session", // "session" (default) | "user" | "workspace"
|
|
589
|
+
memory: "user", // "user" | "workspace" | false; session-only agent reach defaults to false
|
|
590
|
+
create: { sandboxBackend: "none" }, // raw create-request passthrough for a pure chat
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
const reply = await chat.send("hello"); // creates the session on the first send
|
|
594
|
+
console.log(reply.text); // or String(reply); the answer, without progress commentary
|
|
595
|
+
|
|
596
|
+
// Per message: model policy and model-visible page context (not shown in the transcript).
|
|
597
|
+
await chat.send("Why did revenue dip?", {
|
|
598
|
+
model: "gpt-5.5",
|
|
599
|
+
reasoningEffort: "low",
|
|
600
|
+
modelContext: "Viewing dashboard 42, range 2026-09-01..2026-09-28",
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
for await (const chunk of chat.stream("and then?")) {
|
|
604
|
+
if (chunk.type === "text") process.stdout.write(chunk.text);
|
|
605
|
+
if (chunk.type === "pending") await chat.respond({ requestId: chunk.pending.requestId, decision: "approve" });
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Your endpoint. `resolve` is your auth hook: identity comes from the request
|
|
609
|
+
// you authenticated, never from the body. The handler reads the client's
|
|
610
|
+
// conversation id itself (x-opengeni-conversation header, or the wire format's
|
|
611
|
+
// own field) and authorizes as `user`; return `conversation` from resolve only
|
|
612
|
+
// when the host names it, which is required when there is no `user`.
|
|
613
|
+
export const handler = createChatHandler(og, {
|
|
614
|
+
resolve: async (request) => {
|
|
615
|
+
const session = await getSessionFromCookie(request);
|
|
616
|
+
if (!session) return new Response("Unauthorized", { status: 401 });
|
|
617
|
+
return { tenant: session.accountId, user: session.userId };
|
|
618
|
+
},
|
|
619
|
+
// format: "vercel" | "openai-chat" | "openai-responses" (default "native");
|
|
620
|
+
// a request may override it with the x-opengeni-chat-format header.
|
|
621
|
+
});
|
|
622
|
+
export const GET = handler; // conversation history, for restoring the chat on reload
|
|
623
|
+
export const POST = handler; // send a message, or answer a pending request at .../respond
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
Conversation IDs do not change with the acting user. Authorized collaborators
|
|
627
|
+
can use the same session; private visibility prevents access by other users.
|
|
628
|
+
Use `chatBySessionId` for existing and legacy user-namespaced conversations.
|
|
629
|
+
A user is added to the workspace on their first request (see
|
|
630
|
+
[External users](#external-users-asuser)). Without a `user`, the host must name the conversation
|
|
631
|
+
from `resolve`. The Vercel and OpenAI adapters send only the latest user
|
|
632
|
+
message; the earlier messages in that request are imported once, as context on
|
|
633
|
+
the first message of a conversation, after which Opengeni owns the history.
|
|
634
|
+
|
|
635
|
+
Pick the isolation per session with `agentAccess` (which other sessions the
|
|
636
|
+
agent may reach) and `memory` (what it remembers), all inside one workspace that
|
|
637
|
+
shares the customer's documents, instructions, and integrations:
|
|
638
|
+
|
|
639
|
+
| Scenario | `agentAccess` | `memory` |
|
|
640
|
+
| ------------------------------------------------- | ------------- | ------------- |
|
|
641
|
+
| Agent confined to its chat tree (support desk) | `"session"` | `false` |
|
|
642
|
+
| One user's chats see each other, not other users' | `"user"` | `"user"` |
|
|
643
|
+
| Everything in the tenant shared | `"workspace"` | `"workspace"` |
|
|
644
|
+
| Shared agent access, no memory | any | `false` |
|
|
645
|
+
|
|
646
|
+
To keep an existing `useChat` route with its own request body, stream the chat
|
|
647
|
+
into your AI SDK response yourself. The Vercel format speaks the v1 UI message
|
|
648
|
+
stream used by AI SDK 5, 6, and 7 (tool approval requests need 6 or later):
|
|
649
|
+
|
|
650
|
+
```ts
|
|
651
|
+
import { createUIMessageStream, createUIMessageStreamResponse } from "ai";
|
|
652
|
+
import { uiMessageStreamParts } from "@opengeni/sdk/chat";
|
|
653
|
+
|
|
654
|
+
export async function POST(request: Request) {
|
|
655
|
+
const { messages, dashboardId } = await request.json(); // your own body
|
|
656
|
+
const me = await authenticate(request);
|
|
657
|
+
const chat = await og.chat({ tenant: me.accountId, user: me.userId, conversation: me.chatId });
|
|
658
|
+
const text = lastUserText(messages);
|
|
659
|
+
const stream = createUIMessageStream({
|
|
660
|
+
execute: async ({ writer }) => {
|
|
661
|
+
const chunks = chat.stream(text, { modelContext: `Dashboard ${dashboardId}` });
|
|
662
|
+
for await (const part of uiMessageStreamParts(chunks, { framing: false })) {
|
|
663
|
+
writer.write(part as never);
|
|
664
|
+
}
|
|
665
|
+
},
|
|
666
|
+
});
|
|
667
|
+
return createUIMessageStreamResponse({ stream });
|
|
668
|
+
}
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
Opengeni's own tool activity is omitted by default because those tools are not
|
|
672
|
+
in your typed tool set; pass `toolParts: true` (or `createChatHandler(og, {
|
|
673
|
+
toolParts: true })`) to emit them as `dynamic`, provider-executed tool parts.
|
|
674
|
+
Pending approvals are always emitted as dynamic tool parts with a
|
|
675
|
+
`tool-approval-request`.
|
|
676
|
+
|
|
677
|
+
The chat handler is backend-only: connect a custom or compatible frontend to
|
|
678
|
+
its protocol. For the full React agent experience, use `SessionConversation`
|
|
679
|
+
with `OpenGeniClient` and authenticated session routes, not this simplified
|
|
680
|
+
chat protocol. The normal SDK preserves files, tools, approvals with policies,
|
|
681
|
+
forks, and realtime voice.
|
|
682
|
+
|
|
250
683
|
## Related-work discovery
|
|
251
684
|
|
|
252
685
|
`listAgentTopology` returns a bounded hierarchy page plus provider-neutral
|
|
@@ -286,6 +719,35 @@ the advisory UI without changing stored evidence or API authority. See
|
|
|
286
719
|
[`docs/work-discovery.md`](../../docs/work-discovery.md) for ranking, bounds,
|
|
287
720
|
claim lifecycle, and rollout semantics.
|
|
288
721
|
|
|
722
|
+
## Full-history message search
|
|
723
|
+
|
|
724
|
+
`searchSessionMessages(workspaceId, { query, sessionId?, groupBy?, archiveStatus?, limit?,
|
|
725
|
+
cursor? }, { signal? })` searches durable user and **completed assistant** text,
|
|
726
|
+
including unloaded history. It returns every non-overlapping literal occurrence,
|
|
727
|
+
not tools, reasoning, model context, or delta-only assistant output.
|
|
728
|
+
|
|
729
|
+
Each match has a durable `eventId`/`sequence`, a UTF-16 `messageMatchOffset`, and
|
|
730
|
+
`snippet: { text, matchStart, matchEnd }` with original-text UTF-16 offsets.
|
|
731
|
+
Results include `nextCursor`, `hasMore`, cumulative `matchedMessageCount` and
|
|
732
|
+
`matchedOccurrenceCount`, `scannedMessages`, and `countIsExact`. Keep following
|
|
733
|
+
cursors even on empty pages while `hasMore` is true; counts are provisional until
|
|
734
|
+
exhaustion. Cancel superseded searches with the third argument's AbortSignal.
|
|
735
|
+
This is a live, authorized traversal rather than a frozen snapshot; restart to
|
|
736
|
+
refresh after concurrent history or visibility changes.
|
|
737
|
+
|
|
738
|
+
For workspace search, `groupBy: "session"` returns the first hit per matching
|
|
739
|
+
session and skips that session's remaining history, keeping prolific messages
|
|
740
|
+
from filling multiple picker pages. It cannot be combined with `sessionId`.
|
|
741
|
+
In grouped mode both matched counters count session representatives, not full
|
|
742
|
+
per-session message/occurrence totals; `scannedMessages` counts only visited
|
|
743
|
+
messages. Cursors bind the grouping mode. Omit it for every-occurrence Find.
|
|
744
|
+
|
|
745
|
+
The method and types are exported from the ordinary SDK and browser entry.
|
|
746
|
+
Use `listEventPage` around the hit's sequence for bounded context, and use the
|
|
747
|
+
returned search snippet when a large source message's ordinary projection does
|
|
748
|
+
not include the hit. Never download complete history to perform Find locally.
|
|
749
|
+
See [the API contract and bounds](../../docs/session-message-search.md).
|
|
750
|
+
|
|
289
751
|
## Session visibility and forks
|
|
290
752
|
|
|
291
753
|
For organizations with session-tenancy activation, a canonical managed-cookie
|
|
@@ -321,49 +783,143 @@ user decision. A quiescence conflict identifies the stable blocker after live
|
|
|
321
783
|
turns, goals, realtime, schedules, workspace writers, retained processes, and
|
|
322
784
|
sandbox access have been settled. Forks copy exact same-workspace durable
|
|
323
785
|
conversation content but no live turn, goal, credential, Connection/delegation,
|
|
324
|
-
personal grant,
|
|
786
|
+
personal grant, vault policy, task vault, Sandbox Environment, MCP server configuration, process, sandbox
|
|
325
787
|
identity, pin, or workflow. Destination visibility and acknowledgement are
|
|
326
788
|
idempotency-bound.
|
|
327
789
|
|
|
328
|
-
##
|
|
790
|
+
## Archived session history import (server only)
|
|
329
791
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
792
|
+
Use `@opengeni/sdk/session-history-import` to migrate historical conversations
|
|
793
|
+
into read-only archives. These functions take `client` first, use its ordinary
|
|
794
|
+
`requestJson` transport, and are not eager client methods or root helper exports:
|
|
795
|
+
|
|
796
|
+
Create requires `sessions:create`; append requires `sessions:control` and the same
|
|
797
|
+
authenticated importer. With `asUser`, the user's permissions apply rather than
|
|
798
|
+
the organization key's permissions.
|
|
334
799
|
|
|
335
800
|
```ts
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
801
|
+
import { OpenGeniClient } from "@opengeni/sdk";
|
|
802
|
+
import {
|
|
803
|
+
importArchivedSession,
|
|
804
|
+
appendArchivedSessionEvents,
|
|
805
|
+
} from "@opengeni/sdk/session-history-import";
|
|
806
|
+
|
|
807
|
+
const og = new OpenGeniClient({
|
|
808
|
+
baseUrl: process.env.OPENGENI_API_BASE_URL!,
|
|
809
|
+
apiKey: process.env.OPENGENI_API_KEY!,
|
|
339
810
|
});
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
811
|
+
// Existing tenant mapping; the original creator is admitted on first use.
|
|
812
|
+
const actor = og.asUser(originalExternalUserId, { source });
|
|
813
|
+
const imported = await importArchivedSession(actor, workspaceId, {
|
|
814
|
+
importId: "embedded:thread-42", // Stable; persist the exact request before sending.
|
|
815
|
+
title: "Original conversation",
|
|
816
|
+
createdAt: "2025-04-01T09:30:00Z",
|
|
817
|
+
visibility: "user_private",
|
|
818
|
+
});
|
|
819
|
+
const appended = await appendArchivedSessionEvents(actor, workspaceId, imported.importId, {
|
|
820
|
+
batchId: "thread-42:batch-0001", // Stable; also persist this exact body first.
|
|
821
|
+
offset: imported.nextOffset,
|
|
822
|
+
events: [{
|
|
823
|
+
type: "agent.message.completed",
|
|
824
|
+
createdAt: "2025-04-01T09:31:00Z",
|
|
825
|
+
payload: { text: "Historical answer", channel: "final" },
|
|
826
|
+
}],
|
|
827
|
+
});
|
|
828
|
+
// Persist imported.session.id and appended.nextOffset in the migration ledger.
|
|
829
|
+
```
|
|
830
|
+
|
|
831
|
+
External mapping equivalents take `(client, source, externalId, ...)`:
|
|
832
|
+
`importExternalWorkspaceArchivedSession` and
|
|
833
|
+
`appendExternalWorkspaceArchivedSessionEvents`. These resolve an existing
|
|
834
|
+
mapping; neither import form creates a workspace or grants membership. A bare
|
|
835
|
+
organization key creates a shared, ownerless archive. Use `asUser` for the
|
|
836
|
+
verified creator/owner; private imports require existing private-session
|
|
837
|
+
enablement. Preserve source visibility rather than widening it on failure.
|
|
838
|
+
|
|
839
|
+
The subpath exports `ImportArchivedSessionRequest/Response`,
|
|
840
|
+
`AppendArchivedSessionEventsRequest/Response`, `ArchivedSessionImportEvent` and
|
|
841
|
+
`SessionImportedArchive` types. Requests accept at most 100 events / 1 MiB
|
|
842
|
+
serialized UTF-8 JSON and 256 KiB per event. IDs and titles are bounded to 200
|
|
843
|
+
characters. Import events default to `[]`; append batches must be non-empty.
|
|
844
|
+
Events carry `{ type, createdAt, turnId?, payload }`: a finite supported historical
|
|
845
|
+
type, ISO timestamp, optional UUID/null correlation and JSON-object payload.
|
|
846
|
+
Source timestamps support at most millisecond precision; normalize finer dates
|
|
847
|
+
explicitly and retain originals in the ledger. Negative-zero JSON is rejected.
|
|
848
|
+
|
|
849
|
+
Exact create replay returns `created: false`; exact append replay returns
|
|
850
|
+
`replayed: true`. `offset` is a zero-based imported-event count, not a timeline
|
|
851
|
+
sequence. Retain the exact actor, mapping, IDs, offset and bodies for uncertain
|
|
852
|
+
retries; new batches use the acknowledged `nextOffset`. Changed key reuse or
|
|
853
|
+
an out-of-order new offset returns 409. Helpers propagate `OpenGeniApiError`,
|
|
854
|
+
including `outcomeUnknown`, and do not automatically retry mutations.
|
|
855
|
+
|
|
856
|
+
Import is timeline-only: completed messages, calls/results and goals may be
|
|
857
|
+
historical facts, but no model-facing history, live goal, pending decision or
|
|
858
|
+
execution is restored. `session.importedArchive` has
|
|
859
|
+
`{ importId, importedAt, readOnly: true }`, independent of personal archive state.
|
|
860
|
+
Render through the unchanged `SessionConversation` and session proxy; never
|
|
861
|
+
offer Send or Steer. Continuing an imported archive is unsupported in v1.
|
|
862
|
+
The proxy does not expose import routes. Re-upload files through existing APIs
|
|
863
|
+
and replace references before import; preserve additional source metadata and
|
|
864
|
+
source/destination IDs in the host ledger. See
|
|
865
|
+
[Migrating from embedded Opengeni](../../docs/product-integration.md#migrating-from-embedded-opengeni).
|
|
866
|
+
|
|
867
|
+
## Connected accounts
|
|
868
|
+
|
|
869
|
+
Authenticated messages use the initiating user's eligible connected accounts.
|
|
870
|
+
Conversation visibility does not share account access. Queued work, retries and
|
|
871
|
+
child work retain the initiating user. No per-conversation connection grant is
|
|
872
|
+
required. Multiple eligible accounts require an explicit account choice:
|
|
873
|
+
|
|
874
|
+
```ts
|
|
875
|
+
const accounts = await browserClient.listOwnConnectionAccounts(workspaceId);
|
|
876
|
+
// When choosing among multiple accounts, use the exact eligible server/account pair.
|
|
877
|
+
const selection = { serverId: "mail", connectionId: accounts[0]!.id };
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
General personal-resource grants for documents and other resource
|
|
881
|
+
kinds remain available through the root/core SDK. They do not authorize native
|
|
882
|
+
connected accounts.
|
|
883
|
+
|
|
884
|
+
## Workspace credentials, webhooks, and sandbox image
|
|
885
|
+
|
|
886
|
+
Configure these with an organization key or workspace admin session. Secrets are
|
|
887
|
+
returned once; store them when you create the resource. Protocol and payloads:
|
|
888
|
+
[`docs/workspace-integrations.md`](../../docs/workspace-integrations.md).
|
|
889
|
+
|
|
890
|
+
```ts
|
|
891
|
+
const { secret: providerSecret } = await client.putWorkspaceCredentialProvider(workspaceId, {
|
|
892
|
+
url: "https://product.example/opengeni/credentials",
|
|
893
|
+
});
|
|
894
|
+
const { secret: webhookSecret } = await client.createWorkspaceWebhook(workspaceId, {
|
|
895
|
+
url: "https://product.example/opengeni/events",
|
|
896
|
+
eventTypes: ["turn.completed", "turn.failed"],
|
|
351
897
|
});
|
|
352
898
|
|
|
353
|
-
|
|
899
|
+
// In your HTTP handlers, verify the raw body before parsing it:
|
|
900
|
+
const { event } = await verifyWebhookEvent({ body: rawBody, headers, secret: webhookSecret });
|
|
901
|
+
const request = await verifyCredentialProviderRequest({
|
|
902
|
+
body: rawBody,
|
|
903
|
+
headers,
|
|
904
|
+
secret: providerSecret,
|
|
905
|
+
});
|
|
354
906
|
```
|
|
355
907
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
908
|
+
To check an endpoint now, `testWorkspaceWebhook(client, workspaceId, webhookId)` sends a
|
|
909
|
+
signed `webhook.test` event (acknowledge it with any 2xx) and
|
|
910
|
+
`testWorkspaceCredentialProvider(client, workspaceId)` sends a request with
|
|
911
|
+
`purpose: "test"`; both come from `@opengeni/sdk/workspace-integrations` and return
|
|
912
|
+
what the endpoint answered, naming returned credentials but never their values.
|
|
913
|
+
|
|
914
|
+
`listWorkspaceSandboxImages` returns the deployment's allowlisted images; set one
|
|
915
|
+
with `updateWorkspaceSettings(workspaceId, { defaultSandboxImage })`.
|
|
360
916
|
|
|
361
|
-
##
|
|
917
|
+
## Personal schedules
|
|
362
918
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
919
|
+
A schedule created by an authenticated human or their active agent records that
|
|
920
|
+
human as its immutable owner. Only that owner or their verified agent may edit,
|
|
921
|
+
run, pause or delete it. Each occurrence resolves the owner's current accounts;
|
|
922
|
+
accepted retries retain their original identity and account selection.
|
|
367
923
|
|
|
368
924
|
```ts
|
|
369
925
|
const task = await client.createScheduledTask(workspaceId, {
|
|
@@ -371,18 +927,27 @@ const task = await client.createScheduledTask(workspaceId, {
|
|
|
371
927
|
schedule: { type: "calendar", hour: 8, minute: 0, timeZone: "Europe/Oslo" },
|
|
372
928
|
runMode: "reusable_session",
|
|
373
929
|
agentConfig: { prompt: "Triage the new support issues" },
|
|
374
|
-
|
|
930
|
+
connectionAccounts: [selection],
|
|
375
931
|
});
|
|
376
932
|
```
|
|
377
933
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
array
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
934
|
+
Account selections narrow eligible accounts; they never transfer ownership.
|
|
935
|
+
Omitting `connectionAccounts` on update preserves the selection. Passing an
|
|
936
|
+
empty array clears explicit account choices without changing the schedule owner.
|
|
937
|
+
Service-owned schedules retain service execution and do not acquire a human's
|
|
938
|
+
personal accounts. Run history remains credential-free.
|
|
939
|
+
|
|
940
|
+
A schedule freezes its connectors, accounts and (when an agent created it)
|
|
941
|
+
Opengeni tools. For the owner, `listScheduledTasks` and `getScheduledTask`
|
|
942
|
+
include a read-only `policyDrift` naming what is out of date, and
|
|
943
|
+
`listScheduledTaskRuns` includes `accessFailures` for runs that could not use a
|
|
944
|
+
connector. `listScheduledTaskAccessAttention` lists schedules whose latest run
|
|
945
|
+
failed that way, and schedules that cannot start because a chosen account can no
|
|
946
|
+
longer be used (`unavailableAccounts`, with a null `runId`). A signed-in owner
|
|
947
|
+
re-freezes with their current access through
|
|
948
|
+
`refreshScheduledTaskAccess(workspaceId, taskId, { executionDigest, leaveOut })`,
|
|
949
|
+
where the optional `leaveOut` keeps named default connectors or Opengeni tools
|
|
950
|
+
off; API keys and agents cannot. See [`docs/scheduled-task-access.md`](../../docs/scheduled-task-access.md).
|
|
386
951
|
|
|
387
952
|
Deleting a task is externally idempotent and immediately removes it from live
|
|
388
953
|
lists and quota, but the server retains a tombstone plus run/session/turn audit
|
|
@@ -454,7 +1019,7 @@ import {
|
|
|
454
1019
|
createBrowserEditableArtifactSession,
|
|
455
1020
|
} from "@opengeni/sdk/editable-artifacts";
|
|
456
1021
|
|
|
457
|
-
const client = new OpenGeniClient({ baseUrl:
|
|
1022
|
+
const client = new OpenGeniClient({ baseUrl: process.env.OPENGENI_API_BASE_URL! });
|
|
458
1023
|
const artifact = await client.getEditableArtifact(workspaceId, artifactId, {
|
|
459
1024
|
replicaId,
|
|
460
1025
|
});
|
|
@@ -465,7 +1030,7 @@ const kernels = {
|
|
|
465
1030
|
} as const;
|
|
466
1031
|
const { editableArtifactKernelRuntime } = await kernels[artifact.modality]();
|
|
467
1032
|
const session = createBrowserEditableArtifactSession({
|
|
468
|
-
baseUrl:
|
|
1033
|
+
baseUrl: process.env.OPENGENI_API_BASE_URL!,
|
|
469
1034
|
workspaceId,
|
|
470
1035
|
artifact,
|
|
471
1036
|
storageAuthority,
|
|
@@ -503,7 +1068,7 @@ shown below; it owns no state or authority of its own.
|
|
|
503
1068
|
|
|
504
1069
|
`OpenGeniClient.interaction` is the framework-free resource facade over the same
|
|
505
1070
|
public BrowserSession, ComputerSession, identity, auth, network-route, and human
|
|
506
|
-
intervention APIs used by
|
|
1071
|
+
intervention APIs used by Opengeni itself. It adds no second state or execution
|
|
507
1072
|
path: facade methods call the ordinary typed transport, and every mutation still
|
|
508
1073
|
uses the canonical generation fences, operation receipt, permission check, and
|
|
509
1074
|
placement controller.
|
|
@@ -518,6 +1083,8 @@ const browser = await client.interaction.browsers.currentOrOpen({
|
|
|
518
1083
|
const { targets } = await browser.tabs.list();
|
|
519
1084
|
const target = targets.find((candidate) => candidate.selected) ?? targets[0]!;
|
|
520
1085
|
const page = await browser.observe(target.id);
|
|
1086
|
+
const screenshot = await browser.screenshot(target.id, { fullPage: true });
|
|
1087
|
+
// screenshot.data is bounded image bytes; mediaType and dimensions travel with it.
|
|
521
1088
|
|
|
522
1089
|
const receipt = await browser.act({
|
|
523
1090
|
operationId: crypto.randomUUID(),
|
|
@@ -534,6 +1101,17 @@ if (receipt.state === "outcome_unknown") {
|
|
|
534
1101
|
}
|
|
535
1102
|
```
|
|
536
1103
|
|
|
1104
|
+
`browser.targetState(targetId)` reads generation fences without an accessibility
|
|
1105
|
+
tree. `browser.readDom(targetId, request)` returns a bounded element or count
|
|
1106
|
+
query using those fences; sensitive values are redacted.
|
|
1107
|
+
|
|
1108
|
+
The flat `client.openBrowserTargetWithInventory(workspaceId, browserSessionId,
|
|
1109
|
+
{ url })` explicitly opens a tab and returns the owned tab inventory and its
|
|
1110
|
+
generation fences without a page observation. The default
|
|
1111
|
+
`client.openBrowserTarget(...)` and `browser.tabs.open(...)` still return page
|
|
1112
|
+
observations. Older controllers can refuse the inventory method; do not retry an
|
|
1113
|
+
uncertain creation through another tab-open method.
|
|
1114
|
+
|
|
537
1115
|
Browser identities are immutable version graphs: live browser state stays
|
|
538
1116
|
private until `browser.publishRevision(...)` explicitly creates a new revision.
|
|
539
1117
|
Protected auth fills resolve connection secrets only inside the broker/controller
|
|
@@ -561,6 +1139,27 @@ never enter the iframe; `@opengeni/sdk/site` uses a single parent-owned
|
|
|
561
1139
|
`MessagePort` that exposes only the retained tool allowlist intersected with the
|
|
562
1140
|
current viewer's live workspace catalog.
|
|
563
1141
|
|
|
1142
|
+
Site AI can return directly into an app without any conversation UI:
|
|
1143
|
+
|
|
1144
|
+
```ts
|
|
1145
|
+
import { createOpenGeniSiteClient } from "@opengeni/sdk/site";
|
|
1146
|
+
const site = createOpenGeniSiteClient();
|
|
1147
|
+
const result = await site.ai.run({ initialMessage: appPrompt, idempotencyKey: actionId });
|
|
1148
|
+
if (result.status === "completed") renderResult(result.text);
|
|
1149
|
+
```
|
|
1150
|
+
|
|
1151
|
+
The app defines the prompt, context and output presentation. This helper uses
|
|
1152
|
+
native sessions and events with the same permissions, approvals, model access
|
|
1153
|
+
and billing; it is not a second executor. New Site work is private per viewer
|
|
1154
|
+
and excluded from ordinary workspace discovery. `pending` and `cancelled` are
|
|
1155
|
+
not successful results; model failure rejects. Retain the `onSession` handle
|
|
1156
|
+
to observe again using `site.ai.result(id, { turnId })`, without creating work.
|
|
1157
|
+
An `after` cursor returns only subsequent output; omit it for full replay.
|
|
1158
|
+
Aborting observation does not cancel native execution. See
|
|
1159
|
+
[`site-conversations.md`](../../docs/site-conversations.md) and
|
|
1160
|
+
[`site-session-embed`](../../examples/site-session-embed) for optional chat and
|
|
1161
|
+
headless inline examples. Do not store private handles in shared Site Data.
|
|
1162
|
+
|
|
564
1163
|
```ts
|
|
565
1164
|
let cursor: string | undefined;
|
|
566
1165
|
do {
|
|
@@ -586,6 +1185,38 @@ model MCP calls and Codemode. Generated declarations augment the lazy namespace
|
|
|
586
1185
|
for exact installed-tool types; canonical `{ serverId, toolName }` identity and
|
|
587
1186
|
the server-side catalog remain authoritative.
|
|
588
1187
|
|
|
1188
|
+
Known calls use `POST tools/invoke` directly, with no preceding workspace
|
|
1189
|
+
catalog request. The API selects one exact authorized account/connector; a
|
|
1190
|
+
selected MCP provider may still require its own `tools/list`. Symbolic paths
|
|
1191
|
+
use the canonical registry projection and reject ambiguity before connecting.
|
|
1192
|
+
Cold calls use the current definition. `$resolve({ identity })` returns one
|
|
1193
|
+
entry and a `definitionDigest`; pass it as `expectedDefinitionDigest` to require
|
|
1194
|
+
that complete public definition before execution. Digests never grant access.
|
|
1195
|
+
Returned executed metadata preserves structured-output unwrapping without a
|
|
1196
|
+
catalog load.
|
|
1197
|
+
|
|
1198
|
+
`$catalog()` and `$declarations()` remain explicit full discovery. For an older
|
|
1199
|
+
API or Site host, set `toolGatewayMode: "catalog"` explicitly on the client;
|
|
1200
|
+
there is no automatic downgrade or retry after an unknown invocation outcome.
|
|
1201
|
+
The targeted SDK caches bounded per-target metadata, shares only resolution
|
|
1202
|
+
loads, and retries at most once after a typed pre-execution
|
|
1203
|
+
`tool_definition_stale`; explicit nonretryable or outcome-unknown flags veto
|
|
1204
|
+
replay even when a transport labels an error stale. Explicit pins are not
|
|
1205
|
+
silently dropped when malformed: only `undefined` means no explicit pin.
|
|
1206
|
+
The same retry veto applies to retained v1/catalog mode and Site host adapters.
|
|
1207
|
+
For saved Site bundles with code-only retry classifiers, the host's legacy call
|
|
1208
|
+
projection wraps executed stale-named provider errors as
|
|
1209
|
+
`site_tool_execution_failed`, retaining the complete original diagnostic at
|
|
1210
|
+
`result.structuredContent.error.providerError`. Success outputs and ordinary
|
|
1211
|
+
provider errors are unchanged; genuine known preexecution stale refresh remains
|
|
1212
|
+
available. Stale-named transport errors without that proof use
|
|
1213
|
+
`site_tool_call_failed` and retain the original code in their message.
|
|
1214
|
+
Pins are not silently advanced. An approved call uses the server's executable-effect and
|
|
1215
|
+
private-authority binding, not an implicit full-public-entry pin; supply
|
|
1216
|
+
`expectedDefinitionDigest` when that additional precondition is wanted.
|
|
1217
|
+
Changed executable effect or private authority requires reapproval. Operation IDs are correlation
|
|
1218
|
+
keys, not general idempotency for unapproved calls.
|
|
1219
|
+
|
|
589
1220
|
```ts
|
|
590
1221
|
const tools = client.tools.forWorkspace(workspaceId);
|
|
591
1222
|
const documents = await tools.docs.search({ query: "launch plan" });
|
|
@@ -593,7 +1224,13 @@ const documents = await tools.docs.search({ query: "launch plan" });
|
|
|
593
1224
|
|
|
594
1225
|
An approval-required call needs a server-issued capability. A trusted host
|
|
595
1226
|
shows its approval UI first, then asks for one token bound to the current human,
|
|
596
|
-
workspace, operation id,
|
|
1227
|
+
workspace, operation id, exact tool identity, executable effect, Site context,
|
|
1228
|
+
private authority and arguments. Targeted approvals use a distinct version-2 binding, never a v1
|
|
1229
|
+
catalog digest.
|
|
1230
|
+
An Ask-to-Allow policy transition can settle matching pending provenance with
|
|
1231
|
+
or without the token, but never ignores a supplied invalid token or a consumed
|
|
1232
|
+
operation. Public definition pins remain optional and may reject policy/prose
|
|
1233
|
+
drift without consuming the pending approval.
|
|
597
1234
|
The token expires after five minutes, is stored only as a hash, and is consumed
|
|
598
1235
|
once by the matching call.
|
|
599
1236
|
|
|
@@ -605,7 +1242,7 @@ preflight credential and resource authorization before consuming the token.
|
|
|
605
1242
|
const operationId = crypto.randomUUID();
|
|
606
1243
|
const identity = { serverId: "linear", toolName: "issues_update" };
|
|
607
1244
|
const input = { issueId, state: "Done" };
|
|
608
|
-
const approval = await tools.$
|
|
1245
|
+
const approval = await tools.$approveTarget(identity, input, { operationId });
|
|
609
1246
|
|
|
610
1247
|
await tools.$call(identity, input, {
|
|
611
1248
|
operationId,
|
|
@@ -613,7 +1250,13 @@ await tools.$call(identity, input, {
|
|
|
613
1250
|
});
|
|
614
1251
|
```
|
|
615
1252
|
|
|
616
|
-
|
|
1253
|
+
The retained `$approve()` method and explicit `toolGatewayMode: "catalog"` use
|
|
1254
|
+
the legacy protocol described below. Tokens returned by `$approve()` on the
|
|
1255
|
+
same facade retain that routing; externally retained legacy tokens must pass
|
|
1256
|
+
`approvalBinding: "catalog"`. Never reinterpret a legacy token as a targeted
|
|
1257
|
+
approval.
|
|
1258
|
+
|
|
1259
|
+
When a legacy call or approval request receives the typed
|
|
617
1260
|
`409 details.code = "catalog_stale"` response, the SDK refreshes the catalog,
|
|
618
1261
|
re-resolves the exact identity or generated namespace path, preserves the
|
|
619
1262
|
operation id (generating it before the first attempt when omitted), and retries
|
|
@@ -622,6 +1265,19 @@ single-use capability after the catalog digest changes; instead it throws
|
|
|
622
1265
|
`OpenGeniToolReapprovalRequiredError` with the refreshed identity and digest so
|
|
623
1266
|
the trusted host can show approval again for the same operation id.
|
|
624
1267
|
|
|
1268
|
+
Concurrent callers share one in-flight catalog request (`$catalog` and every
|
|
1269
|
+
call path). Each caller's `signal` cancels only its own wait; the shared request
|
|
1270
|
+
is cancelled only when every waiter has aborted, and a failed load is not
|
|
1271
|
+
cached. Calls rejected as stale on the same digest share one refresh started
|
|
1272
|
+
after the first such rejection while it remains the current catalog, and an
|
|
1273
|
+
explicit `$catalog({ refresh: true })` always starts a new request.
|
|
1274
|
+
|
|
1275
|
+
A call's `catalogDigest` is checked against the exact catalog entry it invokes:
|
|
1276
|
+
the API may accept an unchanged entry from a catalog digest whose unrelated
|
|
1277
|
+
entries have since changed, and then echoes that digest in the response. A
|
|
1278
|
+
changed, removed, or no-longer-authorized target entry still returns
|
|
1279
|
+
`catalog_stale` before execution.
|
|
1280
|
+
|
|
625
1281
|
Reapproval is valid only before the original capability is consumed. Once a
|
|
626
1282
|
call crosses that boundary, the server retains a hash-only tombstone for the
|
|
627
1283
|
operation id and rejects another approval with
|
|
@@ -637,7 +1293,16 @@ version and viewer's live authority. The requested set is a maximum allowlist,
|
|
|
637
1293
|
not an authority grant; ordinary gateway approval still applies. Archived Sites
|
|
638
1294
|
receive no tool bridge.
|
|
639
1295
|
|
|
640
|
-
|
|
1296
|
+
An upgraded host advertises targeted calls to modern Site clients. Existing
|
|
1297
|
+
bundled Site clients retain their catalog/call protocol: the host obtains a
|
|
1298
|
+
bounded manifest for only that saved version's requested identities and
|
|
1299
|
+
translates calls using retained per-entry pins. They still make their initial
|
|
1300
|
+
catalog request; republishing with the modern client removes it. Unknown or
|
|
1301
|
+
evicted manifest digests fail before execution. Host-owned Site/version context
|
|
1302
|
+
and approval tokens are stripped from iframe requests. Local previews instead
|
|
1303
|
+
use their frozen attempt catalog and existing Codemode operation journal.
|
|
1304
|
+
|
|
1305
|
+
Omit `firstPartyMcpTools` for the complete Opengeni tool catalog. An explicit
|
|
641
1306
|
`[]` exposes no broad first-party tools; attached resources and separately
|
|
642
1307
|
selected `files`/`docs` MCP servers are unaffected.
|
|
643
1308
|
|
|
@@ -691,6 +1356,53 @@ unexpected successful non-JSON response to a mutation has `outcomeUnknown:
|
|
|
691
1356
|
true` because the mutation might already have been accepted. Never turn that
|
|
692
1357
|
condition into a new operation by changing its idempotency key.
|
|
693
1358
|
|
|
1359
|
+
### Transient read retry
|
|
1360
|
+
|
|
1361
|
+
Safe JSON reads (`GET` and `HEAD`) that fail transiently are retried quietly
|
|
1362
|
+
inside a small budget, so an API rolling deploy or a short network blip does
|
|
1363
|
+
not surface as an error. A failure is transient when the request never got a
|
|
1364
|
+
response (`fetch` rejected with a `TypeError`, such as a reset connection) or a
|
|
1365
|
+
gateway answered `502`, `503` or `504`. Retries use jittered exponential
|
|
1366
|
+
backoff, or the server's `Retry-After` when it fits the budget, and stop as
|
|
1367
|
+
soon as the caller's signal aborts. Every attempt of one call carries the same
|
|
1368
|
+
correlation ID. Mutations are never retried; they keep the `outcomeUnknown`
|
|
1369
|
+
semantics above. Auth, permission, not-found, validation, rate-limit and other
|
|
1370
|
+
server errors, and a success response the client cannot accept, surface
|
|
1371
|
+
immediately. A `Retry-After` gets up to 20% extra spread so clients told the
|
|
1372
|
+
same instant do not return together.
|
|
1373
|
+
|
|
1374
|
+
```ts
|
|
1375
|
+
const client = new OpenGeniClient({
|
|
1376
|
+
baseUrl,
|
|
1377
|
+
// Defaults: 4 attempts, 250 ms first backoff, 2000 ms backoff cap, 5000 ms total.
|
|
1378
|
+
readRetry: { maxAttempts: 4, initialDelayMs: 250, maxDelayMs: 2_000, maxElapsedMs: 5_000 },
|
|
1379
|
+
// readRetry: false turns it off.
|
|
1380
|
+
});
|
|
1381
|
+
```
|
|
1382
|
+
|
|
1383
|
+
`isTransientRequestError(error)` reports whether an error is this kind of
|
|
1384
|
+
transient failure, for callers that keep their own longer retry loop.
|
|
1385
|
+
|
|
1386
|
+
While a deployment is parked for an update, the API answers every request with
|
|
1387
|
+
`{"error":"maintenance"}` (normally HTTP 503). The client decodes it into an
|
|
1388
|
+
`OpenGeniApiError` with `code: "maintenance"` (`OPENGENI_MAINTENANCE_ERROR_CODE`),
|
|
1389
|
+
`retryable: true` and `outcomeUnknown: false`: nothing ran, so a mutation can be
|
|
1390
|
+
retried with the same idempotency key. Its message carries no support
|
|
1391
|
+
reference. `isMaintenanceError(error)` detects it, and `isTransientRequestError`
|
|
1392
|
+
treats it as transient. A park can last minutes, so keep retrying it at a calm
|
|
1393
|
+
pace rather than reporting a failure.
|
|
1394
|
+
|
|
1395
|
+
```ts
|
|
1396
|
+
import { isMaintenanceError } from "@opengeni/sdk";
|
|
1397
|
+
|
|
1398
|
+
try {
|
|
1399
|
+
await client.sendMessage(workspaceId, sessionId, { text, clientEventId });
|
|
1400
|
+
} catch (error) {
|
|
1401
|
+
if (isMaintenanceError(error)) showUpdating(); // retry later with the same clientEventId
|
|
1402
|
+
else throw error;
|
|
1403
|
+
}
|
|
1404
|
+
```
|
|
1405
|
+
|
|
694
1406
|
## Streaming guarantees
|
|
695
1407
|
|
|
696
1408
|
`client.streamEvents(...)` (and the underlying `streamSessionEvents`) delivers
|
|
@@ -769,8 +1481,17 @@ for await (const event of client.streamEvents(workspaceId, sessionId, {
|
|
|
769
1481
|
## Messages, the turn queue, and steering
|
|
770
1482
|
|
|
771
1483
|
Messages sent while a turn is running **queue by default** — visible,
|
|
772
|
-
editable, reorderable, and deletable until the worker claims them
|
|
773
|
-
the
|
|
1484
|
+
editable, reorderable, and deletable until the worker claims them, and they run
|
|
1485
|
+
after the whole running turn. Steering is the explicit alternative: the running
|
|
1486
|
+
turn reads the message within seconds and nothing in progress is killed. A
|
|
1487
|
+
model answer still being written is cut short (its completed text stays), short
|
|
1488
|
+
tool calls finish, and long command or session waits return early while the
|
|
1489
|
+
work keeps running. The turn then hands over (`turn.superseded` with reason
|
|
1490
|
+
`steer`, `atModelStep: true` and, when an answer was cut short, `cutOffText`)
|
|
1491
|
+
and the Steer runs next with the full history. The session screens show it
|
|
1492
|
+
only as the person's message, and the queue snapshot does not list it.
|
|
1493
|
+
`steerQueueItem` with `interrupt: true` on the turn that `steerMessage`
|
|
1494
|
+
returned stops the running attempt now instead, including tool calls in flight.
|
|
774
1495
|
|
|
775
1496
|
```ts
|
|
776
1497
|
// Queue (default): stacks behind the running turn.
|
|
@@ -779,12 +1500,20 @@ await client.sendMessage(workspaceId, sessionId, {
|
|
|
779
1500
|
clientEventId: crypto.randomUUID(),
|
|
780
1501
|
});
|
|
781
1502
|
|
|
782
|
-
// Steer: send + promote to the queue front
|
|
783
|
-
await client.steerMessage(workspaceId, sessionId, {
|
|
1503
|
+
// Steer: send + promote to the queue front; read within seconds.
|
|
1504
|
+
const steered = await client.steerMessage(workspaceId, sessionId, {
|
|
784
1505
|
text: "Stop — prod is paging, look at that first",
|
|
785
1506
|
clientEventId: crypto.randomUUID(),
|
|
786
1507
|
});
|
|
787
1508
|
|
|
1509
|
+
// Hard interrupt: stop the running attempt for that Steer now, including
|
|
1510
|
+
// tool calls in flight (rarely needed; the Steer is read within seconds).
|
|
1511
|
+
await client.steerQueueItem(workspaceId, sessionId, steered.turn.id, {
|
|
1512
|
+
expectedTurnVersion: steered.turn.version,
|
|
1513
|
+
interrupt: true,
|
|
1514
|
+
clientEventId: crypto.randomUUID(),
|
|
1515
|
+
});
|
|
1516
|
+
|
|
788
1517
|
// Manage the server-authoritative queue while it waits.
|
|
789
1518
|
const queue = await client.getQueue(workspaceId, sessionId);
|
|
790
1519
|
const waiting = queue.items.at(-1)!;
|
|
@@ -814,9 +1543,46 @@ await client.cancelSession(workspaceId, sessionId, {
|
|
|
814
1543
|
reason: "host record deleted",
|
|
815
1544
|
clientEventId: crypto.randomUUID(),
|
|
816
1545
|
});
|
|
1546
|
+
// approvalId is `approvals[].id` from the latest `session.requiresAction`
|
|
1547
|
+
// event (type SessionApprovalRequest: { id, name, arguments }); it is the
|
|
1548
|
+
// pending tool call id, not the event id.
|
|
817
1549
|
await client.sendApprovalDecision(workspaceId, sessionId, { approvalId, decision: "approve" });
|
|
818
1550
|
```
|
|
819
1551
|
|
|
1552
|
+
## Retry a failed session without changing its intent
|
|
1553
|
+
|
|
1554
|
+
Try again is separate from Pause/Resume and `sendMessage`. Retain the request
|
|
1555
|
+
across an ambiguous transport response; never synthesize a continuation prompt:
|
|
1556
|
+
|
|
1557
|
+
```ts
|
|
1558
|
+
const retryRequest = {
|
|
1559
|
+
clientEventId: crypto.randomUUID(),
|
|
1560
|
+
failureEventId, // the current durable turn.failed event id
|
|
1561
|
+
model: selectedModel,
|
|
1562
|
+
reasoningEffort: "high" as const,
|
|
1563
|
+
latencyMode: "standard" as const,
|
|
1564
|
+
};
|
|
1565
|
+
const retry = await client.retrySession(workspaceId, sessionId, retryRequest);
|
|
1566
|
+
// { outcome: "accepted" | "replayed", turnId, failureEventId }
|
|
1567
|
+
```
|
|
1568
|
+
|
|
1569
|
+
Requires `sessions:control` and session access. Recovery keeps the logical turn,
|
|
1570
|
+
original question, frozen authority, and completed history/tool results; it
|
|
1571
|
+
does not create a user message. Omitted policy fields retain the failed turn's
|
|
1572
|
+
selection. The selected policy is revalidated for new admission. A duplicate
|
|
1573
|
+
operation replays its receipt even after execution advances.
|
|
1574
|
+
|
|
1575
|
+
HTTP 409 codes distinguish `RETRY_STALE_FAILURE`, `RETRY_EXECUTION_UNRESOLVED`,
|
|
1576
|
+
`RETRY_TOOL_OUTCOME_UNKNOWN`, `RETRY_PAUSED`, `RETRY_UNSUPPORTED_FAILURE`, and
|
|
1577
|
+
`IDEMPOTENCY_KEY_REUSED`. `RETRY_EXECUTION_UNRESOLVED` is transient: earlier
|
|
1578
|
+
execution is still settling. `RETRY_TOOL_OUTCOME_UNKNOWN` is permanent: a tool
|
|
1579
|
+
call in the turn has an unknown outcome and cannot be blindly replayed, so
|
|
1580
|
+
continue with a new message instead. A call proven never started does not block
|
|
1581
|
+
Retry. A deliberate Pause must
|
|
1582
|
+
be resumed separately. Failure without a retained logical turn, a settled
|
|
1583
|
+
scheduled occurrence, and idle credit exhaustion are not supported retry
|
|
1584
|
+
boundaries. See [run lifecycle](../../docs/run-lifecycle.md).
|
|
1585
|
+
|
|
820
1586
|
## Session tool policy and native web search
|
|
821
1587
|
|
|
822
1588
|
For standalone credential maintenance, use the dedicated operation rather than
|
|
@@ -848,7 +1614,7 @@ receipt without a second write. Old receipt verification fails explicitly with
|
|
|
848
1614
|
Omitting `tools` when creating a top-level session selects the current
|
|
849
1615
|
workspace-default capability policy, including the built-in `files` server.
|
|
850
1616
|
Passing `tools`, including `[]`, is an intentional fixed narrowing and can
|
|
851
|
-
therefore disable file-download access for that session.
|
|
1617
|
+
therefore disable file-download access for that session. Opengeni's own web UI
|
|
852
1618
|
keeps `files` enabled as a hidden default, while API and embedded clients retain
|
|
853
1619
|
exact control over the explicit list. Supported Responses providers attach
|
|
854
1620
|
their native bounded web-search tool independently of this MCP policy.
|
|
@@ -866,7 +1632,7 @@ const updated = await client.updateSessionToolPolicy(workspaceId, sessionId, {
|
|
|
866
1632
|
```
|
|
867
1633
|
|
|
868
1634
|
To keep a fixed allow-list, replace both connected MCP servers and individual
|
|
869
|
-
|
|
1635
|
+
Opengeni tools atomically:
|
|
870
1636
|
|
|
871
1637
|
```ts
|
|
872
1638
|
await client.updateSessionToolPolicy(workspaceId, sessionId, {
|
|
@@ -885,6 +1651,7 @@ it is not public web search.
|
|
|
885
1651
|
|
|
886
1652
|
```ts
|
|
887
1653
|
const goal = await client.getGoal(workspaceId, sessionId); // counters: autoContinuations, noProgressStreak
|
|
1654
|
+
const maybeGoal = await client.findGoal(workspaceId, sessionId); // null for a goal-less session (no 404)
|
|
888
1655
|
await client.pauseGoal(workspaceId, sessionId, { rationale: "manual review" });
|
|
889
1656
|
await client.resumeGoal(workspaceId, sessionId); // resets counters, re-arms continuations
|
|
890
1657
|
|
|
@@ -906,7 +1673,7 @@ await client.rollbackGoalRevision(workspaceId, sessionId, appliedRevisionId, {
|
|
|
906
1673
|
call; the lower-level steps are exported for resumable/custom flows.
|
|
907
1674
|
|
|
908
1675
|
Browser hosts need no storage credentials or per-application registration.
|
|
909
|
-
|
|
1676
|
+
Opengeni authorizes the workspace request and returns a short-lived,
|
|
910
1677
|
object-scoped signed URL; operators must configure the private object store to
|
|
911
1678
|
allow CORS from `*` so any product embedding the SDK can use that URL.
|
|
912
1679
|
|
|
@@ -982,12 +1749,11 @@ Every public endpoint group has typed methods:
|
|
|
982
1749
|
| Sessions + events | `createSession`, `listSessions`, `listSessionPage`, `listAgentTopology`, `getSession`, `getSessionLineage`, `updateSession`, `listEvents`, `sendEvent`, `sendMessage`, `steerMessage`, `pauseSession`, `resumeSession`, `cancelSession`, `sendApprovalDecision`, `streamEvents`, `openEventStream` |
|
|
983
1750
|
| Machines (bring-your-own-compute) | `listMachines`, `machineMetricsSeries`, `swapActiveSandbox`, `mintEnrollToken`, `lookupDeviceEnrollment`, `approveDeviceEnrollment`, `denyDeviceEnrollment` |
|
|
984
1751
|
| Turn queue | `getQueue`, `moveQueueItem`, `editQueueItem`, `steerQueueItem`, `deleteQueueItem` |
|
|
985
|
-
| Goal | `getGoal`, `updateGoal`, `pauseGoal`, `resumeGoal`, `listGoalRevisions`, `listGoalRevisionPage`, `applyGoalRevision`, `rejectGoalRevision`, `rollbackGoalRevision` |
|
|
986
|
-
| Scheduled tasks | `createScheduledTask`, `listScheduledTasks`, `getScheduledTask`, `updateScheduledTask`, `pauseScheduledTask`, `resumeScheduledTask`, `triggerScheduledTask`, `deleteScheduledTask`, `listScheduledTaskRuns` |
|
|
987
|
-
|
|
|
1752
|
+
| Goal | `getGoal`, `findGoal`, `updateGoal`, `pauseGoal`, `resumeGoal`, `listGoalRevisions`, `listGoalRevisionPage`, `applyGoalRevision`, `rejectGoalRevision`, `rollbackGoalRevision` |
|
|
1753
|
+
| Scheduled tasks | `createScheduledTask`, `listScheduledTasks`, `getScheduledTask`, `updateScheduledTask`, `pauseScheduledTask`, `resumeScheduledTask`, `triggerScheduledTask`, `deleteScheduledTask`, `listScheduledTaskRuns`, `refreshScheduledTaskAccess`, `listScheduledTaskAccessAttention` |
|
|
1754
|
+
| Vaults | `client.vaults(workspaceId)` returns `listVaults`, `getVault`, `createVault`, `updateVault`, `deleteVault`, `syncVault`, `listItems`, `getItem`, `createItem`, `updateItem`, `deleteItem`, `restoreItem`, `moveItem`, `revealField`, `listFieldVersions`, `restoreFieldVersion`, `getSessionSecrets`, `setSessionVaultPolicy`, `installOnePassword`, `listOnePasswordConnections`, `replaceOnePasswordToken`, `disconnectOnePassword`, `listProviderVaults`, `linkProviderVault` and `createManagedProviderVault`. Reads are metadata-only; `revealField` is the one audited plaintext read. Agents use `{{secret:Vault/Item/field}}` references instead of values. Variable Sets were removed in 2.0.0 |
|
|
988
1755
|
| Files | `uploadFile`, `beginFileUpload`, `completeFileUpload`, `getFile`, `createFileDownloadUrl` |
|
|
989
1756
|
| Documents | `createDocumentBase`, `listDocumentBases`, `getDocumentBase`, `addDocument`, `listDocuments`, `reindexDocument`, `searchDocuments`, `searchKnowledge` (effective organization + workspace + immutable initiating-user personal scope) |
|
|
990
|
-
| Packs | `listPacks`, `registerPack`, `getPack`, `enablePack`, `deletePack`, `listPackInstallations` |
|
|
991
1757
|
| Capabilities | `listCapabilities`, `createCapability`, `enableCapability`, `disableCapability`, `discoverMcpCapabilities` |
|
|
992
1758
|
| Plugin packages | `previewPlugin`, `installPlugin`, `previewPluginUninstall`, `uninstallPlugin` |
|
|
993
1759
|
| API Integrations | `listIntegrationDefinitions`, `listApiIntegrations`, `previewApiIntegration`, `startApiIntegrationOAuth`, `installApiIntegration`, `previewApiIntegrationUninstall`, `uninstallApiIntegration`, `listIntegrationFacets`, `configureIntegrationFacet`, `pauseIntegrationFacet`, `resumeIntegrationFacet`, `removeIntegrationFacet`, `browseGoogleDriveFacetSource`, `saveGoogleDriveFacetSource` |
|
|
@@ -1027,15 +1793,18 @@ reconsider — they can change with their counterpart, not with the SDK.
|
|
|
1027
1793
|
|
|
1028
1794
|
## Compatibility
|
|
1029
1795
|
|
|
1030
|
-
Clients and servers are compatible within the same **major**
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
`/v1/config/client`.
|
|
1796
|
+
Clients and servers are compatible within the same **major** version of this
|
|
1797
|
+
SDK; evolution is additive within a major and both sides are tolerant readers.
|
|
1798
|
+
Official server builds expose `serverVersion` on `/healthz` and
|
|
1799
|
+
`/v1/config/client`. A breaking change ships only in a new major, announced in
|
|
1800
|
+
the changelog. Full policy: `docs/design/api-compatibility-policy.md`.
|
|
1034
1801
|
|
|
1035
1802
|
## Proxy through your own API
|
|
1036
1803
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1804
|
+
For the React conversation, use the packaged
|
|
1805
|
+
[`createSessionProxyHandler`](#embed-the-conversation-default). For a custom
|
|
1806
|
+
backend route, keep your organization API key on your server and re-emit the
|
|
1807
|
+
stream to your own browser clients. The re-emitted wire format is identical to Opengeni's SSE
|
|
1039
1808
|
stream, so the browser side can consume it with this same SDK (or a plain
|
|
1040
1809
|
`EventSource`), including resume via `?after=` / `Last-Event-ID`:
|
|
1041
1810
|
|
|
@@ -1043,7 +1812,10 @@ stream, so the browser side can consume it with this same SDK (or a plain
|
|
|
1043
1812
|
// Your server (Hono, Next.js route handler, Bun.serve, workers, ...):
|
|
1044
1813
|
import { OpenGeniClient, proxySessionEventStream } from "@opengeni/sdk";
|
|
1045
1814
|
|
|
1046
|
-
const client = new OpenGeniClient({
|
|
1815
|
+
const client = new OpenGeniClient({
|
|
1816
|
+
baseUrl: process.env.OPENGENI_API_BASE_URL!,
|
|
1817
|
+
apiKey: process.env.OPENGENI_API_KEY!,
|
|
1818
|
+
});
|
|
1047
1819
|
|
|
1048
1820
|
export function GET(request: Request): Response {
|
|
1049
1821
|
// authenticate *your* user, resolve their session id, then:
|
|
@@ -1072,3 +1844,20 @@ Hosts, test doubles, and same-origin proxies must import
|
|
|
1072
1844
|
configuration or contract header. Do not copy its string value: the revision is
|
|
1073
1845
|
an executable compatibility boundary, and `getClientConfig()` intentionally
|
|
1074
1846
|
fails closed when server and SDK revisions differ.
|
|
1847
|
+
|
|
1848
|
+
### Action review facts
|
|
1849
|
+
|
|
1850
|
+
`getToolActionReview(workspaceId, sessionId, approvalId)` returns version 1 facts
|
|
1851
|
+
for an exact prepared connector action: title, selected account/count, material
|
|
1852
|
+
effects, optional provider-sourced examples, bounded fields, reason, status and
|
|
1853
|
+
available actions. Use `sendApprovalDecision` for the existing authenticated
|
|
1854
|
+
human decision. Facts never grant execution authority.
|
|
1855
|
+
|
|
1856
|
+
`getToolReviewDetails(workspaceId, sessionId, approvalId, { actionDigest, path,
|
|
1857
|
+
offset })` pages through the saved selection/values (25 rows per page). The
|
|
1858
|
+
reference is tied to the original action digest and requires the same session
|
|
1859
|
+
visibility permission; it never re-runs a search. Secret/write-only fields stay
|
|
1860
|
+
hidden. A missing legacy snapshot may return 404; other authorization or transport
|
|
1861
|
+
failures must not be interpreted as permission to execute. Responses are private
|
|
1862
|
+
and non-cacheable. Review states distinguish waiting, execution, cancellation,
|
|
1863
|
+
failure and uncertain outcome. Exact stored arguments are never client-editable.
|