@opengeni/sdk 1.3.0 → 1.4.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 (59) hide show
  1. package/README.md +25 -3
  2. package/dist/adapters/next.js +2 -2
  3. package/dist/artifacts.js +4 -4
  4. package/dist/azure-live-transport.d.ts +32 -0
  5. package/dist/browser.js +3 -3
  6. package/dist/chat/index.js +5 -5
  7. package/dist/{chunk-IB6SUE3R.js → chunk-DTIMLEPB.js} +1 -1
  8. package/dist/chunk-DTIMLEPB.js.map +1 -0
  9. package/dist/{chunk-VYCTL62C.js → chunk-GZQSWAKD.js} +1 -1
  10. package/dist/chunk-GZQSWAKD.js.map +1 -0
  11. package/dist/{chunk-2UXZUCEZ.js → chunk-JOI7XCG6.js} +110 -9
  12. package/dist/chunk-JOI7XCG6.js.map +1 -0
  13. package/dist/{chunk-CREM7YRK.js → chunk-TRUFUTDI.js} +2 -2
  14. package/dist/{chunk-CNSPJYF6.js → chunk-TX6ED3AP.js} +210 -6
  15. package/dist/chunk-TX6ED3AP.js.map +1 -0
  16. package/dist/{chunk-ZBICMU4Z.js → chunk-XIQIV4VL.js} +2 -2
  17. package/dist/{chunk-G5NZGPTB.js → chunk-XPWH3HKG.js} +2 -2
  18. package/dist/{chunk-YVH2PH4X.js → chunk-YWLJGEUB.js} +2 -2
  19. package/dist/{chunk-RSKO3CMZ.js → chunk-ZOSRUR2Q.js} +2 -2
  20. package/dist/codex-realtime-controller.d.ts +13 -0
  21. package/dist/codex-realtime-controller.js +3 -1
  22. package/dist/codex-realtime-v3-wire.d.ts +3 -2
  23. package/dist/codex-realtime.d.ts +4 -0
  24. package/dist/core.js +4 -4
  25. package/dist/document-authority.js +3 -3
  26. package/dist/editable-artifacts.js +1 -1
  27. package/dist/index.d.ts +3 -2
  28. package/dist/index.js +55 -18
  29. package/dist/index.js.map +1 -1
  30. package/dist/realtime.d.ts +1 -1
  31. package/dist/realtime.js +248 -2
  32. package/dist/realtime.js.map +1 -1
  33. package/dist/session-proxy-client.d.ts +7 -3
  34. package/dist/session-proxy.d.ts +38 -7
  35. package/dist/session-proxy.js +2 -2
  36. package/dist/site.js +3 -3
  37. package/dist/transcription.d.ts +1 -1
  38. package/dist/types.d.ts +47 -8
  39. package/package.json +3 -3
  40. package/src/azure-live-transport.ts +286 -0
  41. package/src/codex-realtime-controller.ts +148 -4
  42. package/src/codex-realtime-v3-wire.ts +4 -3
  43. package/src/codex-realtime-v3.ts +11 -2
  44. package/src/codex-realtime.ts +9 -1
  45. package/src/index.ts +3 -0
  46. package/src/realtime.ts +15 -6
  47. package/src/session-proxy-client.ts +7 -3
  48. package/src/session-proxy.ts +321 -11
  49. package/src/transcription.ts +3 -0
  50. package/src/types.ts +49 -5
  51. package/dist/chunk-2UXZUCEZ.js.map +0 -1
  52. package/dist/chunk-CNSPJYF6.js.map +0 -1
  53. package/dist/chunk-IB6SUE3R.js.map +0 -1
  54. package/dist/chunk-VYCTL62C.js.map +0 -1
  55. /package/dist/{chunk-CREM7YRK.js.map → chunk-TRUFUTDI.js.map} +0 -0
  56. /package/dist/{chunk-ZBICMU4Z.js.map → chunk-XIQIV4VL.js.map} +0 -0
  57. /package/dist/{chunk-G5NZGPTB.js.map → chunk-XPWH3HKG.js.map} +0 -0
  58. /package/dist/{chunk-YVH2PH4X.js.map → chunk-YWLJGEUB.js.map} +0 -0
  59. /package/dist/{chunk-RSKO3CMZ.js.map → chunk-ZOSRUR2Q.js.map} +0 -0
@@ -11,7 +11,7 @@ import {
11
11
  import {
12
12
  OPENGENI_API_CONTRACT_HEADER,
13
13
  OPENGENI_API_CONTRACT_REVISION
14
- } from "./chunk-IB6SUE3R.js";
14
+ } from "./chunk-DTIMLEPB.js";
15
15
  import {
16
16
  OpenGeniApiError,
17
17
  OpenGeniSetupError
@@ -267,6 +267,7 @@ function downloadSessionProxySiteHtml(client, workspaceId, artifactId, options)
267
267
  }
268
268
 
269
269
  // src/session-proxy.ts
270
+ var SESSION_PROXY_DEFAULT_HEARTBEAT_MS = 5e3;
270
271
  var DEFAULT_MAX_BODY_BYTES = 1024 * 1024;
271
272
  var SEGMENT = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
272
273
  var QUEUE_OPERATIONS = /* @__PURE__ */ new Set(["move", "edit", "steer", "delete"]);
@@ -309,13 +310,14 @@ function createSessionProxyHandler(target, options) {
309
310
  const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
310
311
  const filesEnabled = options.files ?? true;
311
312
  const voiceInputEnabled = options.voiceInput ?? true;
313
+ const realtimeVoiceEnabled = options.realtimeVoice ?? true;
312
314
  const sandboxFilesEnabled = options.sandboxFiles === true;
313
315
  const artifactsEnabled = options.artifacts !== void 0 && options.artifacts !== false;
314
316
  const editableLiveUrl = artifactsEnabled ? liveSocketUrl(
315
317
  (typeof options.artifacts === "object" ? options.artifacts.editableLiveUrl : void 0) ?? service.apiUrl(EDITABLE_ARTIFACT_LIVE_PATH)
316
318
  ) : null;
317
319
  const modelSelection = options.modelSelection ?? true;
318
- const heartbeatMs = options.heartbeatMs ?? 15e3;
320
+ const heartbeatMs = options.heartbeatMs ?? SESSION_PROXY_DEFAULT_HEARTBEAT_MS;
319
321
  const sessionList = options.sessionList ?? "mine";
320
322
  const archiveEnabled = options.archive ?? true;
321
323
  const chats = options.chats ?? "private";
@@ -336,10 +338,10 @@ function createSessionProxyHandler(target, options) {
336
338
  return async (request) => {
337
339
  try {
338
340
  const method = request.method;
339
- if (!["GET", "POST", "PUT", "PATCH"].includes(method)) {
341
+ if (!PROXY_METHODS.includes(method)) {
340
342
  return new Response(null, {
341
343
  status: 405,
342
- headers: { Allow: "GET, POST, PUT, PATCH" }
344
+ headers: { Allow: PROXY_METHODS.join(", ") }
343
345
  });
344
346
  }
345
347
  const resolved = await options.resolve(request);
@@ -438,6 +440,30 @@ function createSessionProxyHandler(target, options) {
438
440
  ]
439
441
  };
440
442
  };
443
+ const refreshRealtimeCredentials = async (sessionId2, updates) => {
444
+ if (updates.length === 0) return;
445
+ try {
446
+ const servers = (await client.getSession(workspaceId, sessionId2, call)).mcpServers ?? [];
447
+ const rotations = updates.flatMap((update) => {
448
+ const server = servers.find((candidate) => candidate.id === update.id);
449
+ return server ? [
450
+ {
451
+ id: update.id,
452
+ expectedCredentialVersion: server.credentialVersion,
453
+ expectedServerUrl: server.url,
454
+ headers: update.headers
455
+ }
456
+ ] : [];
457
+ });
458
+ if (rotations.length === 0) return;
459
+ await client.rotateSessionMcpCredentials(workspaceId, sessionId2, {
460
+ operationKey: crypto.randomUUID(),
461
+ updates: rotations
462
+ });
463
+ } catch (error) {
464
+ if (!(error instanceof OpenGeniApiError)) throw error;
465
+ }
466
+ };
441
467
  const forwardMessage = async (value, input) => {
442
468
  const message = sanitizeMessage(value, modelSelection || input.delivery === "submit");
443
469
  const extras = await messageExtras(input);
@@ -501,7 +527,13 @@ function createSessionProxyHandler(target, options) {
501
527
  // reads it here instead of knowing any Opengeni id.
502
528
  workspaceId,
503
529
  sandboxFiles: sandboxFilesEnabled,
504
- ...artifacts ? { artifacts } : {},
530
+ // Stock UIs show Site previews and the artifact viewer only when
531
+ // this proxy serves them; `false` keeps them from failing on click.
532
+ artifacts: artifacts ?? false,
533
+ // Whether the stock "New chat" and "Archive" actions can succeed.
534
+ sessionCreation: options.createSession !== void 0,
535
+ archive: archiveEnabled,
536
+ ...realtimeVoiceEnabled ? {} : { realtimeVoice: false },
505
537
  // Explicit true also tells stock UIs to offer end users the model picker.
506
538
  ...modelSelection ? options.modelSelection === true ? { modelSelection: true } : {} : { modelSelection: false }
507
539
  });
@@ -526,6 +558,10 @@ function createSessionProxyHandler(target, options) {
526
558
  if (area === "model-catalog" && tail.length === 0 && method === "GET") {
527
559
  return await read(`${base}/model-catalog`);
528
560
  }
561
+ if (area === "realtime-model-catalog" && tail.length === 0 && method === "GET") {
562
+ if (!realtimeVoiceEnabled) return errorJson(404, "route_not_allowed", "Not found.");
563
+ return await read(`${base}/realtime-model-catalog`);
564
+ }
529
565
  if (area === "live-events" && tail.length === 1 && tail[0] === "stream" && method === "GET") {
530
566
  await client.requestJson("GET", base, void 0, {}, call);
531
567
  return workspaceLiveStream(client, workspaceId, url.searchParams, request, heartbeatMs);
@@ -562,6 +598,45 @@ function createSessionProxyHandler(target, options) {
562
598
  }
563
599
  return errorJson(404, "route_not_allowed", "Not found.");
564
600
  }
601
+ if (area === "artifacts") {
602
+ const [artifactId, ...artifactOp] = tail;
603
+ const route2 = `${method} ${artifactOp.join("/")}`;
604
+ if (!filesEnabled || !artifactId || !SEGMENT.test(artifactId) || route2 !== "GET content" && route2 !== "POST playback-source") {
605
+ return errorJson(404, "route_not_allowed", "Not found.");
606
+ }
607
+ const sessionId2 = request.headers.get(SESSION_SCOPE_HEADER)?.trim() ?? "";
608
+ if (!SEGMENT.test(sessionId2)) {
609
+ reject(400, "session_scope_required", "Artifact reads must name their session.");
610
+ }
611
+ if (options.authorizeSession && !await options.authorizeSession(sessionId2, context)) {
612
+ return errorJson(404, "session_not_found", "Session not found.");
613
+ }
614
+ await getSessionProxyArtifactAssociation(
615
+ client,
616
+ workspaceId,
617
+ sessionId2,
618
+ "retained",
619
+ artifactId,
620
+ call
621
+ );
622
+ if (route2 === "POST playback-source") {
623
+ return json(
624
+ await client.requestJson(
625
+ "POST",
626
+ `${base}/artifacts/${artifactId}/playback-source`,
627
+ void 0,
628
+ {},
629
+ call
630
+ )
631
+ );
632
+ }
633
+ return retainedContent(
634
+ await client.getRetainedArtifactContent(workspaceId, artifactId, {
635
+ ...retainedRange(request),
636
+ signal: request.signal
637
+ })
638
+ );
639
+ }
565
640
  if (area === "transcriptions" && tail.length === 0 && method === "POST") {
566
641
  if (!voiceInputEnabled) return errorJson(404, "route_not_allowed", "Not found.");
567
642
  return json(await client.transcribeAudio(workspaceId, await transcriptionInput(request)));
@@ -688,6 +763,31 @@ function createSessionProxyHandler(target, options) {
688
763
  };
689
764
  };
690
765
  const forward = async (path, payload) => payload instanceof Response ? payload : json(await client.requestJson("POST", path, payload));
766
+ if (op[0] === "realtime") {
767
+ if (!realtimeVoiceEnabled) return errorJson(404, "route_not_allowed", "Not found.");
768
+ const realtime = realtimeRoute(method, op.slice(1));
769
+ if (!realtime) return errorJson(404, "route_not_allowed", "Not found.");
770
+ if (!body) reject(400, "invalid_body", "A JSON object body is required.");
771
+ const path = `${session}/${op.join("/")}`;
772
+ if (realtime === "begin") {
773
+ const extras = await messageExtras({
774
+ sessionId,
775
+ delivery: "realtime"
776
+ });
777
+ if (extras instanceof Response) return extras;
778
+ await refreshRealtimeCredentials(sessionId, extras?.mcpCredentialUpdates ?? []);
779
+ }
780
+ let payload = body;
781
+ if (realtime === "sync" && options.beforeForwardMessage && hasRealtimeMessages(body)) {
782
+ const extras = await options.beforeForwardMessage(
783
+ { sessionId, delivery: "realtime" },
784
+ context
785
+ );
786
+ if (extras instanceof Response) return extras;
787
+ payload = withRealtimeContext(body, extras?.modelContext);
788
+ }
789
+ return json(await client.requestJson(method, path, payload));
790
+ }
691
791
  switch (route) {
692
792
  case "GET ":
693
793
  return await read(session);
@@ -765,6 +865,22 @@ function createSessionProxyHandler(target, options) {
765
865
  }
766
866
  case "GET human-input-requests":
767
867
  return await read(`${session}/human-input-requests`);
868
+ case "GET goal":
869
+ return await read(`${session}/goal`);
870
+ case "PATCH goal": {
871
+ const status = body?.status;
872
+ if (status !== "paused" && status !== "active" || Object.keys(body ?? {}).some((key) => key !== "status" && key !== "rationale")) {
873
+ reject(
874
+ 403,
875
+ "goal_update_not_allowed",
876
+ "Only { status: paused | active } is available."
877
+ );
878
+ }
879
+ return json(await client.requestJson("PATCH", `${session}/goal`, { status }));
880
+ }
881
+ case "DELETE goal":
882
+ await client.deleteGoal(workspaceId, sessionId);
883
+ return new Response(null, { status: 204 });
768
884
  case "POST fs/read":
769
885
  case "POST fs/read-workspace": {
770
886
  if (!sandboxFilesEnabled) return errorJson(404, "route_not_allowed", "Not found.");
@@ -773,6 +889,19 @@ function createSessionProxyHandler(target, options) {
773
889
  );
774
890
  }
775
891
  }
892
+ if (op[0] === "artifacts" && (op.length === 2 || op[2] === "content") && method === "GET") {
893
+ if (!filesEnabled || op.length > 3) {
894
+ return errorJson(404, "route_not_allowed", "Not found.");
895
+ }
896
+ await client.getSession(workspaceId, sessionId, call);
897
+ if (op.length === 2) return await read(`${session}/artifacts/${op[1]}`);
898
+ return retainedContent(
899
+ await client.getSessionRetainedArtifactContent(workspaceId, sessionId, op[1], {
900
+ ...retainedRange(request),
901
+ signal: request.signal
902
+ })
903
+ );
904
+ }
776
905
  if (op.length === 2 && op[0] === "human-input-requests" && method === "GET") {
777
906
  return await read(`${session}/human-input-requests/${op[1]}`);
778
907
  }
@@ -785,7 +914,48 @@ function createSessionProxyHandler(target, options) {
785
914
  }
786
915
  };
787
916
  }
917
+ var PROXY_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
788
918
  var UPLOAD_FIELDS = ["scope", "filename", "contentType", "sizeBytes", "sha256"];
919
+ var REALTIME_CONNECT_ROUTES = /* @__PURE__ */ new Set(["webrtc", "gateway", "supergrok"]);
920
+ var REALTIME_MESSAGE_KINDS = /* @__PURE__ */ new Set([
921
+ "delegation_call",
922
+ "user_transcript",
923
+ "assistant_transcript"
924
+ ]);
925
+ function realtimeRoute(method, rest) {
926
+ if (rest.length === 0) return method === "POST" ? "begin" : null;
927
+ if (rest.length === 1) {
928
+ if (REALTIME_CONNECT_ROUTES.has(rest[0])) return method === "POST" ? "connect" : null;
929
+ return method === "DELETE" ? "end" : null;
930
+ }
931
+ if (rest.length === 2 && rest[1] === "heartbeat") return method === "PATCH" ? "heartbeat" : null;
932
+ if (rest.length === 2 && rest[1] === "sync") return method === "POST" ? "sync" : null;
933
+ if (rest.length === 4 && rest[1] === "connections" && rest[3] === "activate") {
934
+ return method === "POST" ? "activate" : null;
935
+ }
936
+ return null;
937
+ }
938
+ function hasRealtimeMessages(body) {
939
+ return Array.isArray(body.entries) && body.entries.some(
940
+ (entry) => entry !== null && typeof entry === "object" && REALTIME_MESSAGE_KINDS.has(entry.kind)
941
+ );
942
+ }
943
+ function withRealtimeContext(body, serverContext) {
944
+ if (!serverContext?.trim() || !Array.isArray(body.entries)) return body;
945
+ return {
946
+ ...body,
947
+ entries: body.entries.map((entry) => {
948
+ if (entry === null || typeof entry !== "object") return entry;
949
+ const record = entry;
950
+ if (!REALTIME_MESSAGE_KINDS.has(record.kind)) return entry;
951
+ const modelContext = joinContext(
952
+ serverContext,
953
+ typeof record.modelContext === "string" ? record.modelContext : void 0
954
+ );
955
+ return modelContext ? { ...record, modelContext } : entry;
956
+ })
957
+ };
958
+ }
789
959
  function normalizeToolServer(toolServer) {
790
960
  const configured = toolServer.url ?? globalThis.process?.env?.OPENGENI_TOOL_SERVER_URL;
791
961
  let url;
@@ -976,6 +1146,31 @@ async function legacyViewerGrant(client, workspaceId) {
976
1146
  if (!grant) reject(403, "workspace_not_allowed", "This workspace is not available.");
977
1147
  return grant;
978
1148
  }
1149
+ function retainedRange(request) {
1150
+ const range = request.headers.get("range");
1151
+ if (range === null) return {};
1152
+ if (range.length > 128 || /[^\x20-\x7e]/.test(range)) {
1153
+ reject(400, "invalid_range", "Range must be at most 128 printable ASCII bytes.");
1154
+ }
1155
+ return { range };
1156
+ }
1157
+ function retainedContent(content) {
1158
+ return new Response(content.bytes, {
1159
+ status: content.status,
1160
+ headers: {
1161
+ "Content-Type": content.contentType,
1162
+ "Content-Length": String(content.contentLength),
1163
+ ...content.contentRange ? { "Content-Range": content.contentRange } : {},
1164
+ "Accept-Ranges": "bytes",
1165
+ "Cache-Control": "private, no-store",
1166
+ // Bytes for the conversation's own reader; never rendered on this origin.
1167
+ "Content-Security-Policy": "sandbox",
1168
+ "Content-Disposition": "attachment",
1169
+ "Cross-Origin-Resource-Policy": "same-origin",
1170
+ "X-Content-Type-Options": "nosniff"
1171
+ }
1172
+ });
1173
+ }
979
1174
  function sandboxRead(body) {
980
1175
  const path = body?.path;
981
1176
  const encoding = body?.encoding;
@@ -1298,6 +1493,14 @@ data: ${JSON.stringify(event)}
1298
1493
  }
1299
1494
  });
1300
1495
  }
1496
+ var warnedRejectedApiKey = false;
1497
+ function warnRejectedApiKey(error) {
1498
+ if (warnedRejectedApiKey) return;
1499
+ warnedRejectedApiKey = true;
1500
+ console.warn(
1501
+ `[@opengeni/sdk] Opengeni rejected the session proxy's API key (401${error.correlationId ? `, reference ${error.correlationId}` : ""}). It may be expired, revoked or mistyped: create a new key in Opengeni under Organization settings > Developer and update OPENGENI_API_KEY on the server.`
1502
+ );
1503
+ }
1301
1504
  function errorResponse(error) {
1302
1505
  if (error instanceof ProxyRejection) return errorJson(error.status, error.code, error.message);
1303
1506
  if (error instanceof OpenGeniSetupError) {
@@ -1314,6 +1517,7 @@ function errorResponse(error) {
1314
1517
  );
1315
1518
  }
1316
1519
  if (error instanceof OpenGeniApiError) {
1520
+ if (error.status === 401) warnRejectedApiKey(error);
1317
1521
  const status = error.status >= 400 && error.status <= 599 ? error.status : 502;
1318
1522
  if (error.body) {
1319
1523
  return new Response(error.body, {
@@ -1365,4 +1569,4 @@ export {
1365
1569
  createSessionProxyHandler,
1366
1570
  artifactViewerCapability
1367
1571
  };
1368
- //# sourceMappingURL=chunk-CNSPJYF6.js.map
1572
+ //# sourceMappingURL=chunk-TX6ED3AP.js.map