@opengeni/sdk 1.0.2-canary.37191796234001 → 1.0.2

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.
Files changed (84) hide show
  1. package/README.md +111 -85
  2. package/dist/adapters/next.js +5 -5
  3. package/dist/artifacts.js +7 -7
  4. package/dist/browser.js +6 -6
  5. package/dist/chat/index.d.ts +1 -1
  6. package/dist/chat/index.js +94 -28
  7. package/dist/chat/index.js.map +1 -1
  8. package/dist/chat/opengeni.d.ts +33 -6
  9. package/dist/chat/types.d.ts +34 -8
  10. package/dist/{chunk-IZA6XTJ2.js → chunk-2UXZUCEZ.js} +3 -3
  11. package/dist/{chunk-IZA6XTJ2.js.map → chunk-2UXZUCEZ.js.map} +1 -1
  12. package/dist/{chunk-D37KNKO6.js → chunk-4SMEOV3P.js} +3 -3
  13. package/dist/{chunk-D37KNKO6.js.map → chunk-4SMEOV3P.js.map} +1 -1
  14. package/dist/{chunk-OP3VOVII.js → chunk-AH5D5Q5V.js} +2 -2
  15. package/dist/{chunk-4UPT3FIF.js → chunk-APXVSSSY.js} +2 -2
  16. package/dist/{chunk-MKLHWLPF.js → chunk-DUVIYER4.js} +6 -6
  17. package/dist/{chunk-MKLHWLPF.js.map → chunk-DUVIYER4.js.map} +1 -1
  18. package/dist/{chunk-6TEGEI4E.js → chunk-FG3OO5LM.js} +16 -13
  19. package/dist/{chunk-6TEGEI4E.js.map → chunk-FG3OO5LM.js.map} +1 -1
  20. package/dist/{chunk-DSVI5QBQ.js → chunk-HMGADJGZ.js} +2 -2
  21. package/dist/{chunk-YKZME56C.js → chunk-KWADQ227.js} +3 -3
  22. package/dist/{chunk-YKZME56C.js.map → chunk-KWADQ227.js.map} +1 -1
  23. package/dist/{chunk-SRWDF5FG.js → chunk-LY2K6ELJ.js} +11 -9
  24. package/dist/chunk-LY2K6ELJ.js.map +1 -0
  25. package/dist/{chunk-EBDZD5XW.js → chunk-MOTJYIYL.js} +8 -3
  26. package/dist/{chunk-EBDZD5XW.js.map → chunk-MOTJYIYL.js.map} +1 -1
  27. package/dist/{chunk-L2JTM5ZK.js → chunk-NC57YMUO.js} +2 -2
  28. package/dist/{chunk-ANKRFHYG.js → chunk-RWKB6YZG.js} +21 -8
  29. package/dist/chunk-RWKB6YZG.js.map +1 -0
  30. package/dist/{chunk-P7BAOGEM.js → chunk-U6CV2SXO.js} +2 -2
  31. package/dist/{chunk-P7BAOGEM.js.map → chunk-U6CV2SXO.js.map} +1 -1
  32. package/dist/{chunk-U54UBB7D.js → chunk-V6GIY62B.js} +3 -3
  33. package/dist/{chunk-U54UBB7D.js.map → chunk-V6GIY62B.js.map} +1 -1
  34. package/dist/{chunk-5VTRBY4Z.js → chunk-WU6GQG25.js} +2 -2
  35. package/dist/{chunk-5VTRBY4Z.js.map → chunk-WU6GQG25.js.map} +1 -1
  36. package/dist/{chunk-LF2ONL44.js → chunk-YIKA2QT4.js} +1 -1
  37. package/dist/chunk-YIKA2QT4.js.map +1 -0
  38. package/dist/client.d.ts +5 -2
  39. package/dist/codex-realtime-controller.js +2 -2
  40. package/dist/core.js +7 -7
  41. package/dist/document-authority.js +6 -6
  42. package/dist/editable-artifacts.js +1 -1
  43. package/dist/embedding-client.d.ts +6 -1
  44. package/dist/gateway-realtime-transport.js +1 -1
  45. package/dist/index.js +15 -15
  46. package/dist/interaction.js +2 -2
  47. package/dist/realtime.js +3 -3
  48. package/dist/session-proxy.d.ts +12 -4
  49. package/dist/session-proxy.js +5 -5
  50. package/dist/site-browser-runtime.gen.d.ts +1 -1
  51. package/dist/site-document.js +1 -1
  52. package/dist/site-document.js.map +1 -1
  53. package/dist/site.js +7 -7
  54. package/dist/tenant-workspaces.d.ts +27 -13
  55. package/dist/tenant-workspaces.js +2 -2
  56. package/dist/tool-auth.js +1 -1
  57. package/dist/{tools-LZISRKDU.js → tools-FXBLUWAE.js} +3 -3
  58. package/dist/types.d.ts +6 -0
  59. package/dist/workspace-integrations.js +1 -1
  60. package/package.json +3 -3
  61. package/src/chat/index.ts +1 -0
  62. package/src/chat/opengeni.ts +95 -20
  63. package/src/chat/types.ts +33 -9
  64. package/src/client.ts +12 -6
  65. package/src/codex-realtime.ts +1 -1
  66. package/src/embedding-client.ts +6 -1
  67. package/src/errors.ts +2 -2
  68. package/src/gateway-realtime-transport.ts +3 -3
  69. package/src/session-proxy.ts +38 -5
  70. package/src/site-browser-runtime.gen.ts +1 -1
  71. package/src/site.ts +2 -2
  72. package/src/tenant-workspaces.ts +54 -26
  73. package/src/tool-auth.ts +1 -1
  74. package/src/tools.ts +1 -1
  75. package/src/types.ts +6 -0
  76. package/src/workspace-integrations.ts +1 -1
  77. package/dist/chunk-ANKRFHYG.js.map +0 -1
  78. package/dist/chunk-LF2ONL44.js.map +0 -1
  79. package/dist/chunk-SRWDF5FG.js.map +0 -1
  80. /package/dist/{chunk-OP3VOVII.js.map → chunk-AH5D5Q5V.js.map} +0 -0
  81. /package/dist/{chunk-4UPT3FIF.js.map → chunk-APXVSSSY.js.map} +0 -0
  82. /package/dist/{chunk-DSVI5QBQ.js.map → chunk-HMGADJGZ.js.map} +0 -0
  83. /package/dist/{chunk-L2JTM5ZK.js.map → chunk-NC57YMUO.js.map} +0 -0
  84. /package/dist/{tools-LZISRKDU.js.map → tools-FXBLUWAE.js.map} +0 -0
package/README.md CHANGED
@@ -52,79 +52,96 @@ change allowances.
52
52
 
53
53
  ## Embed the conversation (default)
54
54
 
55
- The default product integration is the full OpenGeni conversation:
56
- `@opengeni/react`'s `SessionConversation` in the browser, backed by this SDK
57
- through a tenant/user-scoped same-origin proxy on your server.
58
- `createSessionProxyHandler` is that proxy, packaged:
55
+ Embedding Opengeni is one backend route plus one component, and you only speak
56
+ in your own ids. The browser renders `@opengeni/react`'s `OpenGeniChat` (or
57
+ `SessionConversation`); your server mounts `createSessionProxyHandler`, a
58
+ same-origin proxy that acts as your authenticated user:
59
59
 
60
60
  ```ts
61
- // Server: mount at /api/opengeni/* (see the framework adapters below).
62
- import { OpenGeniClient, createSessionProxyHandler } from "@opengeni/sdk";
61
+ // lib/opengeni.ts (server only: the organization API key never reaches the browser)
62
+ import { OpenGeni } from "@opengeni/sdk/chat";
63
63
 
64
- const og = new OpenGeniClient({
65
- baseUrl: process.env.OPENGENI_API_BASE_URL!,
66
- apiKey: process.env.OPENGENI_API_KEY!,
67
- });
64
+ export const og = new OpenGeni({ apiKey: process.env.OPENGENI_API_KEY! });
65
+ ```
66
+
67
+ ```ts
68
+ // app/api/opengeni/[...path]/route.ts (any web-standard host works; see the adapters below)
69
+ import { createSessionProxyRoute } from "@opengeni/sdk/next";
70
+ import { og } from "@/lib/opengeni";
68
71
 
69
- export const handler = createSessionProxyHandler(og, {
72
+ export const dynamic = "force-dynamic";
73
+ export const { GET, POST, PUT, PATCH, DELETE } = createSessionProxyRoute(og, {
70
74
  resolve: async (request) => {
71
- const me = await authenticate(request); // your session/cookie check
75
+ const me = await authenticate(request); // your existing session check
72
76
  if (!me) return new Response("Unauthorized", { status: 401 });
73
- return { workspaceId: me.openGeniWorkspaceId, user: me.userId, source: "acme-app" };
77
+ return { user: me.id, tenant: me.orgId }; // or { user: me.id }
74
78
  },
75
- authorizeMutation: (request) => verifyCsrf(request), // your existing CSRF policy
76
- // Optional. Omit to keep session creation purely server-side.
77
- createSession: ({ initialMessage, idempotencyKey }) => ({
78
- initialMessage,
79
- idempotencyKey: idempotencyKey ?? crypto.randomUUID(),
80
- tools: [{ kind: "mcp", id: "acme" }], // explicit, server-chosen tool selection
81
- firstPartyMcpTools: [],
82
- }),
79
+ createSession: (input) => input, // let the browser start chats
83
80
  });
84
81
  ```
85
82
 
86
83
  ```tsx
87
- // Browser: the unmodified SDK client, pointed at your mount.
88
- import { OpenGeniClient } from "@opengeni/sdk";
89
- import { OpenGeniChat, OpenGeniProvider } from "@opengeni/react";
84
+ // app/assistant/assistant.tsx (browser): no provider, client, or workspace id.
85
+ "use client";
86
+ import { OpenGeniChat } from "@opengeni/react";
90
87
  import "@opengeni/react/compiled.css";
91
88
 
92
- const client = new OpenGeniClient({ baseUrl: "/api/opengeni" });
93
- <OpenGeniProvider client={client} workspaceId={workspaceId}>
94
- {/* The user's chat list plus the conversation; or <SessionConversation sessionId={id} />. */}
95
- <OpenGeniChat />
96
- </OpenGeniProvider>;
89
+ export function Assistant() {
90
+ return <OpenGeniChat baseUrl="/api/opengeni" />; // chat list + conversation
91
+ }
97
92
  ```
98
93
 
99
- The proxy and `@opengeni/sdk/chat` facade accept `chats`. The proxy defaults to
100
- `"private"`; the facade does too when a `user` is present. Without a user and with
101
- `chats` omitted, the facade keeps workspace visibility, session-only agent reach
102
- and Knowledge authoring off. Explicit `"private"` requires an authenticated user.
103
- Private chats use private visibility, session-only agent reach and personal
104
- Knowledge; `"shared"` uses workspace visibility, agent reach and Knowledge.
105
- The API wire values are `visibility: "private" | "workspace"`; stored visibility
106
- is `user_private | workspace_shared`. Explicit hook/create fields override defaults.
107
-
108
- For `"isolated"`, pass the facade and return `{ tenant, user }` from `resolve`.
109
- `og.workspaceIdFor({ tenant, user }, { isolation: "user" })` provisions a separate
110
- workspace and that user's external membership. It uses stable onboarding keys,
111
- so retries do not restore revoked access. Other modes need explicit onboarding.
112
- The standalone resolver is on the server-only `@opengeni/sdk/tenant-workspaces`
113
- subpath. Keep tenant and user values host-authenticated.
114
-
115
- Isolated members get only workspace read, session create/read/control (including
116
- sending messages), file upload/read, and `mcp_servers:attach` for host-provided
117
- per-session servers. No admin permissions are included. Pass `memberPermissions`
118
- to the `OpenGeni` constructor or `createWorkspaceIdResolver` options to replace
119
- that list, for example to disable file uploads or MCP attachment. The organization
120
- key must also permit each operation. This is initial onboarding, not a membership
121
- update: changing the option does not update existing grants or restore revoked access.
122
- Keep MCP URLs and credentials in the server's `createSession` hook; the browser
123
- cannot choose them through the proxy.
124
- An existing onboarding conflict returns the workspace address without retrying
125
- or changing the grant. The address is not authorization: later `asUser` calls
126
- still enforce current permissions. Existing isolated users need an explicit
127
- `updateExternalWorkspaceMember` to gain newly added permissions such as MCP attachment.
94
+ `baseUrl` makes the component create the browser client for your mount and use
95
+ the workspace the proxy resolved for the signed-in user (reported in the
96
+ proxy's client config). One conversation alone is
97
+ `<SessionConversation baseUrl="/api/opengeni" sessionId={id} />`. The
98
+ `<OpenGeniProvider client={client} workspaceId={workspaceId}>` form keeps
99
+ working, for example for several components or the headless hooks.
100
+
101
+ What `resolve` returns decides the workspace:
102
+
103
+ - `{ user, tenant }`: one workspace per tenant; chats are private per user by
104
+ default.
105
+ - `{ user }`: one workspace per user. It stays single-user: the SDK adds its
106
+ owner once, and Opengeni never admits anyone else to it.
107
+ - `{ user, workspaceId }`: bring your own workspace id.
108
+
109
+ Workspaces are created on first use and cached per `OpenGeni` instance.
110
+ `og.workspaceId({ tenant })` and `og.workspaceId({ user })` return the same ids
111
+ the proxy uses. There is no onboarding step: the first request a user makes
112
+ adds them to a tenant (or your own) workspace with conversation permissions
113
+ (workspace read, session create/read/control, file upload/read, and attaching
114
+ your per-session MCP servers; no admin). That needs an organization API key
115
+ with `members:manage` (full access includes it) plus those permissions.
116
+ Opengeni never changes an existing membership, so narrowing or widening one
117
+ with `updateExternalWorkspaceMember` sticks. A user you remove from a tenant
118
+ workspace is added again on their next request, because your product decides
119
+ who its users are; stop resolving a user you no longer admit. A removal from a
120
+ per-user workspace sticks. `organizationId` is optional:
121
+ the facade reads it once from the key. Keep `user` and `tenant`
122
+ host-authenticated, never from the request body or path.
123
+
124
+ The proxy and facade accept `chats`. `"private"` (the default with a user)
125
+ uses private visibility, session-only agent reach and personal Knowledge;
126
+ `"shared"` uses workspace visibility, agent reach and Knowledge, so a tenant's
127
+ users see each other's chats. Without a user and with `chats` omitted, the
128
+ facade keeps workspace visibility, session-only agent reach and Knowledge
129
+ authoring off. The API wire values are `visibility: "private" | "workspace"`;
130
+ stored visibility is `user_private | workspace_shared`. Explicit hook/create
131
+ fields override defaults. Keep MCP URLs and credentials in the server's
132
+ `createSession` hook; the browser cannot choose them through the proxy.
133
+
134
+ You can also pass a plain `OpenGeniClient` (organization key) instead of the
135
+ facade; then `resolve` must return `{ user, workspaceId }`. The explicit
136
+ membership APIs (`ensureWorkspace`, `addExternalWorkspaceMember`,
137
+ `updateExternalWorkspaceMember`, `cancelExternalWorkspaceMemberGrant`) keep
138
+ working for products that manage access themselves. Legacy
139
+ `chats: "isolated"` with `{ tenant, user }` still provisions a separate
140
+ workspace per user and tenant. Per-user workspaces add their owner with a
141
+ stable onboarding key, so a retry never restores a revoked grant;
142
+ `memberPermissions` replaces that owner's permissions on first onboarding
143
+ only. The standalone resolver is on the server-only
144
+ `@opengeni/sdk/tenant-workspaces` subpath.
128
145
 
129
146
  The facade also accepts `agent` (identity, capabilities, instructions, renderer)
130
147
  and defaults its renderer to `"markdown"`. Current servers always admit agent
@@ -260,12 +277,12 @@ still cannot send `mcpCredentialUpdates` itself. With `modelSelection: false`
260
277
  the proxy reports `modelSelection: false` in the client config, and
261
278
  `SessionConversation` hides its model picker.
262
279
 
263
- Onboard each user explicitly before their first request (see
264
- [External users](#external-users-asuser)); the proxy never grants membership.
265
- The conversation needs `workspace:read`, `sessions:read`, `sessions:control`,
266
- plus `sessions:create` when the user creates sessions and `files:upload` /
267
- `files:read` for attachments. Pass the facade from `@opengeni/sdk/chat` instead
268
- of a client to resolve `{ tenant, user }` through `ensureWorkspace`.
280
+ Opengeni adds each user to the workspace on their first request (see
281
+ [External users](#external-users-asuser)), with `workspace:read`,
282
+ `sessions:create`, `sessions:read`, `sessions:control`, `files:upload`,
283
+ `files:read`, and `mcp_servers:attach`. Pass the facade from `@opengeni/sdk/chat`
284
+ instead of a client to resolve `{ tenant, user }` or `{ user }` to a workspace
285
+ created on first use.
269
286
 
270
287
  ## Quick start
271
288
 
@@ -401,17 +418,34 @@ On a product backend, `client.asUser(externalId, { source? })` returns a separat
401
418
  client that sends the external actor assertion with the organization key. IDs
402
419
  are opaque and case-sensitive; the default source is `default`. Keep the key and
403
420
  this client server-side. Each request requires the intersection of the key's
404
- permissions and the user's explicit workspace membership. API-key rotation does
405
- not change the external identity.
421
+ permissions and the user's workspace membership. API-key rotation does not
422
+ change the external identity.
423
+
424
+ Membership is automatic: when an `asUser` request reaches a shared workspace of
425
+ the key's own organization and the user has no membership there, Opengeni adds
426
+ one with conversation permissions (workspace read, session create/read/control,
427
+ file upload/read, `mcp_servers:attach`) and continues the request. This needs
428
+ the same authority as explicit onboarding: the key holds `members:manage` (or a
429
+ legacy `workspace:admin`) and every one of those permissions, with the
430
+ workspace in its scope; otherwise the request is a `403` as before. It never
431
+ applies to linked native identities, Personal workspaces, SDK per-user
432
+ workspaces (`opengeni-sdk:user-isolation:*` external source), other
433
+ organizations, requests that need a permission outside those defaults, or any
434
+ non-key caller, and it never changes an existing membership. The identity is
435
+ re-checked as active under the same lock, and the grant writes the same
436
+ receipt and lifecycle event as explicit onboarding, attributed to the key. A
437
+ user removed from a shared workspace is added again on their next request; a
438
+ suspended or offboarded identity stays refused.
406
439
 
407
440
  Use the service client—not an `asUser` client—to call
408
441
  `addExternalWorkspaceMember(workspaceId, { identity: { externalId, source? }, permissions })`
409
- for explicit onboarding. It requires `members:manage`, cannot grant more than
410
- the key permits, excludes Personal workspaces, and refuses to overwrite an
411
- existing membership with different permissions; change an existing member's permissions with the keyed
442
+ to choose permissions up front. It requires `members:manage`, cannot grant more
443
+ than the key permits, excludes Personal workspaces, and refuses to overwrite an
444
+ existing membership with different permissions (`409`, also when automatic
445
+ membership already created it); change an existing member's permissions with the keyed
412
446
  `updateExternalWorkspaceMember(organizationId, workspaceId, organizationMembershipId,
413
- { operationId, permissions })`, which never tears down work. Ordinary `asUser` calls never
414
- restore removed workspace membership or reactivate disabled identities.
447
+ { operationId, permissions })`, which never tears down work. `asUser` calls never
448
+ reactivate disabled identities.
415
449
 
416
450
  Service `removeWorkspaceMember` supports external members through the existing
417
451
  fenced workspace teardown. Account-wide `updateExternalIdentityMembership(
@@ -464,21 +498,13 @@ import { OpenGeni, createChatHandler } from "@opengeni/sdk/chat";
464
498
 
465
499
  const og = new OpenGeni({
466
500
  baseUrl: process.env.OPENGENI_API_BASE_URL!, // omitted = production https://app.opengeni.ai
467
- apiKey: process.env.OPENGENI_API_KEY!,
468
- organizationId: process.env.OPENGENI_ORGANIZATION_ID!,
501
+ apiKey: process.env.OPENGENI_API_KEY!, // the organization id is derived from the key
469
502
  source: "acme-app", // labels your product's tenants and users (default "app")
470
503
  });
471
504
 
472
- // Once per user, when your product admits them to the tenant. Chat requests
473
- // never grant workspace membership; without it the API answers 403.
474
- await og.client.addExternalWorkspaceMember(await og.workspaceId({ tenant: "acme" }), {
475
- identity: { externalId: "u_42", source: og.source },
476
- permissions: ["workspace:read", "sessions:create", "sessions:read", "sessions:control"],
477
- });
478
-
479
505
  const chat = await og.chat({
480
- tenant: "acme", // one workspace per customer, created on first use
481
- user: "u_42", // authenticated product user; onboard workspace membership first
506
+ tenant: "acme", // one workspace per customer, created on first use; omit for one per user
507
+ user: "u_42", // authenticated product user; added to the workspace on first use
482
508
  conversation: "c_9", // stable conversation id, independent of the acting user
483
509
  agentAccess: "session", // "session" (default) | "user" | "workspace"
484
510
  memory: "user", // "user" | "workspace" | false; session-only agent reach defaults to false
@@ -521,8 +547,8 @@ export const POST = handler; // send a message, or answer a pending request at .
521
547
  Conversation IDs do not change with the acting user. Authorized collaborators
522
548
  can use the same session; private visibility prevents access by other users.
523
549
  Use `chatBySessionId` for existing and legacy user-namespaced conversations.
524
- User mode requires explicit workspace membership and never restores removed
525
- membership automatically. Without a `user`, the host must name the conversation
550
+ A user is added to the workspace on their first request (see
551
+ [External users](#external-users-asuser)). Without a `user`, the host must name the conversation
526
552
  from `resolve`. The Vercel and OpenAI adapters send only the latest user
527
553
  message; the earlier messages in that request are imported once, as context on
528
554
  the first message of a conversation, after which OpenGeni owns the history.
@@ -703,7 +729,7 @@ const og = new OpenGeniClient({
703
729
  baseUrl: process.env.OPENGENI_API_BASE_URL!,
704
730
  apiKey: process.env.OPENGENI_API_KEY!,
705
731
  });
706
- // Existing tenant mapping + explicitly onboarded original creator.
732
+ // Existing tenant mapping; the original creator is admitted on first use.
707
733
  const actor = og.asUser(originalExternalUserId, { source });
708
734
  const imported = await importArchivedSession(actor, workspaceId, {
709
735
  importId: "embedded:thread-42", // Stable; persist the exact request before sending.
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createSessionProxyHandler
3
- } from "../chunk-ANKRFHYG.js";
3
+ } from "../chunk-RWKB6YZG.js";
4
4
  import "../chunk-UM4ZXXJR.js";
5
- import "../chunk-P7BAOGEM.js";
6
- import "../chunk-4UPT3FIF.js";
7
- import "../chunk-LF2ONL44.js";
8
- import "../chunk-U54UBB7D.js";
5
+ import "../chunk-U6CV2SXO.js";
6
+ import "../chunk-APXVSSSY.js";
7
+ import "../chunk-YIKA2QT4.js";
8
+ import "../chunk-V6GIY62B.js";
9
9
 
10
10
  // src/adapters/next.ts
11
11
  function toNextRouteHandlers(handler) {
package/dist/artifacts.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  OpenGeniClient
3
- } from "./chunk-OP3VOVII.js";
4
- import "./chunk-DSVI5QBQ.js";
5
- import "./chunk-6TEGEI4E.js";
6
- import "./chunk-4UPT3FIF.js";
7
- import "./chunk-LF2ONL44.js";
8
- import "./chunk-L2JTM5ZK.js";
9
- import "./chunk-U54UBB7D.js";
3
+ } from "./chunk-AH5D5Q5V.js";
4
+ import "./chunk-HMGADJGZ.js";
5
+ import "./chunk-FG3OO5LM.js";
6
+ import "./chunk-APXVSSSY.js";
7
+ import "./chunk-YIKA2QT4.js";
8
+ import "./chunk-NC57YMUO.js";
9
+ import "./chunk-V6GIY62B.js";
10
10
  export {
11
11
  OpenGeniClient
12
12
  };
package/dist/browser.js CHANGED
@@ -5,16 +5,16 @@ import {
5
5
  OpenGeniToolCallError,
6
6
  OpenGeniToolReapprovalRequiredError,
7
7
  OpenGeniToolsClient
8
- } from "./chunk-D37KNKO6.js";
8
+ } from "./chunk-4SMEOV3P.js";
9
9
  import {
10
10
  OpenGeniClient
11
- } from "./chunk-6TEGEI4E.js";
12
- import "./chunk-4UPT3FIF.js";
11
+ } from "./chunk-FG3OO5LM.js";
12
+ import "./chunk-APXVSSSY.js";
13
13
  import {
14
14
  OPENGENI_API_CONTRACT_HEADER,
15
15
  OPENGENI_API_CONTRACT_REVISION
16
- } from "./chunk-LF2ONL44.js";
17
- import "./chunk-L2JTM5ZK.js";
16
+ } from "./chunk-YIKA2QT4.js";
17
+ import "./chunk-NC57YMUO.js";
18
18
  import {
19
19
  OpenGeniAllowanceExhaustedError,
20
20
  OpenGeniApiContractMismatchError,
@@ -25,7 +25,7 @@ import {
25
25
  OpenGeniStreamError,
26
26
  formatErrorMessage,
27
27
  isRetryableStreamError
28
- } from "./chunk-U54UBB7D.js";
28
+ } from "./chunk-V6GIY62B.js";
29
29
  export {
30
30
  OPENGENI_API_CONTRACT_HEADER,
31
31
  OPENGENI_API_CONTRACT_REVISION,
@@ -1,7 +1,7 @@
1
1
  export type { Chats } from "../chats.js";
2
2
  export { OpenGeniSetupError } from "../errors.js";
3
3
  export { Chat, DEFAULT_CHAT_SOURCE, DEFAULT_OPENGENI_BASE_URL, IMPORTED_HISTORY_MAX_CHARS, OpenGeni, formatImportedHistory, } from "./opengeni.js";
4
- export { OpenGeniChatError, type ChatAgentAccess, type ChatChunk, type ChatImportedMessage, type ChatMemory, type ChatMessage, type ChatOptions, type ChatPending, type ChatReply, type ChatReplyStatus, type ChatRespondInput, type ChatSendOptions, type ChatSessionListOptions, type ChatSnapshot, type ChatTarget, type ChatToolStatus, type OpenGeniOptions, } from "./types.js";
4
+ export { OpenGeniChatError, type ChatAgentAccess, type ChatChunk, type ChatImportedMessage, type ChatMemory, type ChatMessage, type ChatOptions, type ChatPending, type ChatReply, type ChatReplyStatus, type ChatRespondInput, type ChatSendOptions, type ChatSessionListOptions, type ChatSnapshot, type ChatTarget, type WorkspaceTarget, type ChatToolStatus, type OpenGeniOptions, } from "./types.js";
5
5
  export { CHAT_SESSION_NAMESPACE, chatIdempotencyKey, chatIdentityName, chatSessionId, uuidV5, type ChatUserLabel, } from "./ids.js";
6
6
  export { ChatTurnFold, approvalPending, humanInputPending, type ChatFoldStep, type ChatTurnTerminal, } from "./fold.js";
7
7
  export { CHAT_CONVERSATION_HEADER, CHAT_FORMAT_HEADER, chatChunksToSseBlocks, chatChunksToSseResponse, chatChunksToSseStream, createChatHandler, handleNativeChatRequest, handleNativeHistoryRequest, handleNativeRespondRequest, parseChatChunkStream, respondInputFromBody, type ChatHandlerFormat, type ChatHandlerOptions, type ChatResolution, type ChatResolve, } from "./handler.js";
@@ -6,27 +6,27 @@ import {
6
6
  createWorkspaceIdResolver,
7
7
  isUuid,
8
8
  uuidV5
9
- } from "../chunk-SRWDF5FG.js";
9
+ } from "../chunk-LY2K6ELJ.js";
10
10
  import {
11
11
  EMPTY_FINAL_REPLY_NOTICE,
12
12
  OpenGeniEmbeddingClient,
13
13
  turnCompletedWithEmptyFinalReply
14
- } from "../chunk-EBDZD5XW.js";
15
- import "../chunk-OP3VOVII.js";
16
- import "../chunk-DSVI5QBQ.js";
14
+ } from "../chunk-MOTJYIYL.js";
15
+ import "../chunk-AH5D5Q5V.js";
16
+ import "../chunk-HMGADJGZ.js";
17
17
  import {
18
18
  chatDefaults
19
19
  } from "../chunk-UM4ZXXJR.js";
20
- import "../chunk-6TEGEI4E.js";
20
+ import "../chunk-FG3OO5LM.js";
21
21
  import {
22
22
  parseSseStream
23
- } from "../chunk-4UPT3FIF.js";
24
- import "../chunk-LF2ONL44.js";
25
- import "../chunk-L2JTM5ZK.js";
23
+ } from "../chunk-APXVSSSY.js";
24
+ import "../chunk-YIKA2QT4.js";
25
+ import "../chunk-NC57YMUO.js";
26
26
  import {
27
27
  OpenGeniApiError,
28
28
  OpenGeniSetupError
29
- } from "../chunk-U54UBB7D.js";
29
+ } from "../chunk-V6GIY62B.js";
30
30
 
31
31
  // src/chat/types.ts
32
32
  var OpenGeniChatError = class extends Error {
@@ -378,44 +378,116 @@ var IMPORTED_HISTORY_HEADER = "Earlier conversation imported from the product, o
378
378
  var IMPORTED_HISTORY_ROLES = /* @__PURE__ */ new Set(["user", "assistant", "system"]);
379
379
  var OpenGeni = class {
380
380
  client;
381
+ /**
382
+ * The organization id: the one passed to the constructor, else the one
383
+ * derived from the API key once {@link resolveOrganizationId} (or any
384
+ * workspace lookup) has run. An empty string before that; prefer
385
+ * `await og.resolveOrganizationId()`.
386
+ */
381
387
  organizationId;
382
388
  source;
383
389
  sessions;
384
390
  resolveWorkspaceId;
385
391
  implicitAgentAdmission;
392
+ pendingOrganizationId;
386
393
  constructor(options) {
387
- if (!options.apiKey) throw new TypeError("OpenGeni requires an apiKey.");
388
- if (!options.organizationId) throw new TypeError("OpenGeni requires an organizationId.");
394
+ if (!options.apiKey) throw new TypeError("Opengeni requires an apiKey.");
389
395
  this.client = new OpenGeniEmbeddingClient({
390
396
  baseUrl: options.baseUrl ?? DEFAULT_OPENGENI_BASE_URL,
391
397
  apiKey: options.apiKey,
392
398
  ...options.fetch ? { fetch: options.fetch } : {}
393
399
  });
394
- this.organizationId = options.organizationId;
400
+ this.organizationId = options.organizationId ?? "";
395
401
  this.source = options.source ?? DEFAULT_CHAT_SOURCE;
396
402
  this.resolveWorkspaceId = createWorkspaceIdResolver(this.client, {
397
- organizationId: this.organizationId,
403
+ organizationId: () => this.resolveOrganizationId(),
398
404
  source: this.source,
399
405
  workspaceName: options.workspaceName,
400
406
  memberPermissions: options.memberPermissions
401
407
  });
402
408
  this.sessions = { list: (listOptions) => this.listSessions(listOptions) };
403
409
  }
404
- /** The workspace id for a tenant (created on first use, cached per instance) or an explicit id. */
410
+ /**
411
+ * The organization that owns every workspace this facade creates. Uses the
412
+ * constructor's `organizationId`, else reads it once from the API key
413
+ * (`GET /v1/access/me`) and caches it.
414
+ */
415
+ async resolveOrganizationId() {
416
+ if (this.organizationId) return this.organizationId;
417
+ if (!this.pendingOrganizationId) {
418
+ const pending = this.client.getAccessContext().then((access) => {
419
+ const credential = access.credential;
420
+ if (credential && credential.kind !== "organization_api_key") {
421
+ throw new TypeError(
422
+ "OpenGeni needs an organization API key to create workspaces; this is a workspace key."
423
+ );
424
+ }
425
+ const organizationId = credential?.accountId ?? access.defaultAccountId;
426
+ if (!organizationId) {
427
+ throw new TypeError(
428
+ "Could not derive the organization from this API key. Pass organizationId."
429
+ );
430
+ }
431
+ this.organizationId = organizationId;
432
+ return organizationId;
433
+ });
434
+ pending.catch(() => {
435
+ if (this.pendingOrganizationId === pending) this.pendingOrganizationId = void 0;
436
+ });
437
+ this.pendingOrganizationId = pending;
438
+ }
439
+ return await this.pendingOrganizationId;
440
+ }
441
+ /**
442
+ * Translate your own ids to the Opengeni workspace id, creating the
443
+ * workspace on first use (cached per instance):
444
+ * `{ tenant }` is one workspace per tenant, `{ user }` (no tenant) is one
445
+ * workspace per user, and `{ workspaceId }` is returned as is. Opengeni adds
446
+ * a tenant workspace's users on their first request; a per-user workspace
447
+ * gets its one owner from the SDK and admits nobody else.
448
+ */
405
449
  async workspaceId(target) {
450
+ if (target.workspaceId === "" || target.tenant === "") {
451
+ throw new TypeError("tenant and workspaceId must be non-empty ids.");
452
+ }
406
453
  if (target.workspaceId) return target.workspaceId;
407
- const tenant = target.tenant;
408
- if (!tenant) throw new TypeError("Pass either tenant or workspaceId.");
409
- return await this.workspaceIdFor({ tenant }, { isolation: "tenant" });
454
+ if (target.tenant) {
455
+ return await this.workspaceIdFor({ tenant: target.tenant }, { isolation: "tenant" });
456
+ }
457
+ if ("tenant" in target || "workspaceId" in target) {
458
+ throw new TypeError(
459
+ "tenant or workspaceId is undefined. Pass a non-empty id, or omit the key for one workspace per user."
460
+ );
461
+ }
462
+ const user = "user" in target ? target.user : void 0;
463
+ if (user) {
464
+ return await this.workspaceIdFor({ user }, { isolation: "user" });
465
+ }
466
+ throw new TypeError("Pass tenant, user, or workspaceId.");
410
467
  }
411
- /** Resolve a tenant workspace, or provision its separate user workspace and external member. */
468
+ /**
469
+ * Resolve a tenant workspace or a user's own workspace (user isolation, per
470
+ * tenant when one is given). A user's own workspace is provisioned with that
471
+ * user as its only explicit member.
472
+ */
412
473
  async workspaceIdFor(target, options) {
413
474
  return await this.resolveWorkspaceId(target, options);
414
475
  }
476
+ /** `chats: "isolated"` keeps its tenant-plus-user workspace; a user alone gets their own. */
477
+ async isolatedWorkspaceId(target) {
478
+ if (target.workspaceId) {
479
+ throw new TypeError('chats: "isolated" requires a tenant or user, not a workspaceId.');
480
+ }
481
+ return await this.workspaceIdFor(
482
+ { tenant: target.tenant, user: target.user },
483
+ { isolation: "user" }
484
+ );
485
+ }
415
486
  /**
416
487
  * Address one conversation; the session is created lazily on the first send.
417
- * Identity selects authority, not the conversation address. Workspace
418
- * membership must be provisioned by the host's explicit onboarding flow.
488
+ * Identity selects authority, not the conversation address. The workspace
489
+ * is the tenant's, the user's own (user only), or the explicit id; Opengeni
490
+ * adds the user to it on their first request.
419
491
  * Legacy user-namespaced conversations remain accessible by their session ID.
420
492
  */
421
493
  async chat(options) {
@@ -442,10 +514,7 @@ var OpenGeni = class {
442
514
  );
443
515
  }
444
516
  const client = options.user ? this.client.asUser(options.user, { source: this.source }) : this.client;
445
- const workspaceId = options.chats === "isolated" ? await this.workspaceIdFor(
446
- { tenant: options.tenant ?? "", user: options.user },
447
- { isolation: "user" }
448
- ) : await this.workspaceId(options);
517
+ const workspaceId = options.chats === "isolated" ? await this.isolatedWorkspaceId(options) : await this.workspaceId(options);
449
518
  const sessionId = options.sessionId ?? await chatSessionId(workspaceId, options.conversation);
450
519
  const session = await this.findSession(client, workspaceId, sessionId);
451
520
  const buildCreate = (text, send) => {
@@ -537,10 +606,7 @@ var OpenGeni = class {
537
606
  }
538
607
  }
539
608
  async listSessions(options) {
540
- const workspaceId = options.chats === "isolated" ? await this.workspaceIdFor(
541
- { tenant: options.tenant ?? "", user: options.user },
542
- { isolation: "user" }
543
- ) : await this.workspaceId(options);
609
+ const workspaceId = options.chats === "isolated" ? await this.isolatedWorkspaceId(options) : await this.workspaceId(options);
544
610
  const client = options.user ? this.client.asUser(options.user, { source: this.source }) : this.client;
545
611
  return await client.requestJson(
546
612
  "GET",