@opengeni/sdk 3.3.2 → 3.7.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/README.md +80 -6
  2. package/dist/artifact-client.d.ts +4 -2
  3. package/dist/artifacts.d.ts +1 -1
  4. package/dist/artifacts.js +6 -6
  5. package/dist/browser.d.ts +5 -2
  6. package/dist/browser.js +14 -4
  7. package/dist/{chunk-YXQX7RQT.js → chunk-2E2BFEEN.js} +2 -2
  8. package/dist/{chunk-JUS2UYRP.js → chunk-3QDQTYOE.js} +3 -1
  9. package/dist/chunk-3QDQTYOE.js.map +1 -0
  10. package/dist/{chunk-C2H7HBNP.js → chunk-7S2MZ65L.js} +6 -6
  11. package/dist/chunk-FMLDFJ2Q.js +246 -0
  12. package/dist/chunk-FMLDFJ2Q.js.map +1 -0
  13. package/dist/{chunk-TRNI7KEO.js → chunk-IQTEPOI3.js} +228 -26
  14. package/dist/chunk-IQTEPOI3.js.map +1 -0
  15. package/dist/chunk-IRAEPLXG.js +350 -0
  16. package/dist/chunk-IRAEPLXG.js.map +1 -0
  17. package/dist/{chunk-QT3KFVDP.js → chunk-MPGVC7EO.js} +10 -2
  18. package/dist/chunk-MPGVC7EO.js.map +1 -0
  19. package/dist/{chunk-C4DJPZRU.js → chunk-OO5LPTO7.js} +14 -1
  20. package/dist/chunk-OO5LPTO7.js.map +1 -0
  21. package/dist/{chunk-KKRO2VCP.js → chunk-QTBAMHEF.js} +2 -2
  22. package/dist/client.d.ts +51 -11
  23. package/dist/codex-realtime-controller.js +2 -2
  24. package/dist/core.d.ts +5 -2
  25. package/dist/core.js +17 -7
  26. package/dist/document-authority.js +5 -5
  27. package/dist/editable-artifacts.js +4 -4
  28. package/dist/errors.d.ts +12 -0
  29. package/dist/github-repositories.d.ts +10 -0
  30. package/dist/github-repositories.js +36 -0
  31. package/dist/github-repositories.js.map +1 -0
  32. package/dist/index.d.ts +13 -6
  33. package/dist/index.js +99 -52
  34. package/dist/index.js.map +1 -1
  35. package/dist/interaction.js +2 -2
  36. package/dist/model-context.d.ts +49 -0
  37. package/dist/model-picker-order.d.ts +1 -0
  38. package/dist/model-picker-order.js +10 -0
  39. package/dist/model-picker-order.js.map +1 -0
  40. package/dist/proxy.d.ts +7 -5
  41. package/dist/realtime.js +2 -2
  42. package/dist/session-titles.d.ts +2 -28
  43. package/dist/site.d.ts +93 -0
  44. package/dist/site.js +35 -0
  45. package/dist/site.js.map +1 -0
  46. package/dist/stream.d.ts +2 -0
  47. package/dist/tools-FSTY4FAY.js +12 -0
  48. package/dist/tools-FSTY4FAY.js.map +1 -0
  49. package/dist/tools.d.ts +61 -0
  50. package/dist/types.d.ts +231 -9
  51. package/dist/workspace-artifacts.d.ts +26 -1
  52. package/package.json +14 -2
  53. package/src/artifact-client.ts +16 -1
  54. package/src/artifacts.ts +1 -0
  55. package/src/browser.ts +16 -0
  56. package/src/client.ts +332 -22
  57. package/src/core.ts +16 -0
  58. package/src/errors.ts +23 -0
  59. package/src/github-repositories.ts +60 -0
  60. package/src/index.ts +94 -1
  61. package/src/model-context.ts +69 -0
  62. package/src/model-picker-order.ts +6 -0
  63. package/src/proxy.ts +17 -6
  64. package/src/session-titles.ts +8 -82
  65. package/src/site.ts +544 -0
  66. package/src/stream.ts +29 -8
  67. package/src/tools.ts +396 -0
  68. package/src/types.ts +287 -10
  69. package/src/workspace-artifacts.ts +33 -2
  70. package/dist/chunk-C4DJPZRU.js.map +0 -1
  71. package/dist/chunk-JUS2UYRP.js.map +0 -1
  72. package/dist/chunk-QT3KFVDP.js.map +0 -1
  73. package/dist/chunk-TRNI7KEO.js.map +0 -1
  74. /package/dist/{chunk-YXQX7RQT.js.map → chunk-2E2BFEEN.js.map} +0 -0
  75. /package/dist/{chunk-C2H7HBNP.js.map → chunk-7S2MZ65L.js.map} +0 -0
  76. /package/dist/{chunk-KKRO2VCP.js.map → chunk-QTBAMHEF.js.map} +0 -0
package/src/errors.ts CHANGED
@@ -49,6 +49,29 @@ export class OpenGeniApiError extends Error {
49
49
  }
50
50
  }
51
51
 
52
+ export type OpenGeniSecureContextRequiredReason = "insecure_context" | "web_crypto_unavailable";
53
+
54
+ /**
55
+ * A browser operation requires secure-context-only platform capabilities.
56
+ * Callers may key UI behavior on the stable `secure_context_required` code;
57
+ * `reason` exists only to keep the human guidance truthful.
58
+ */
59
+ export class OpenGeniSecureContextRequiredError extends Error {
60
+ readonly code = "secure_context_required" as const;
61
+ readonly retryable = false;
62
+ readonly reason: OpenGeniSecureContextRequiredReason;
63
+
64
+ constructor(reason: OpenGeniSecureContextRequiredReason) {
65
+ super(
66
+ reason === "insecure_context"
67
+ ? "Couldn’t attach this file because OpenGeni is open over HTTP. Attachments require a secure HTTPS connection. Open the secure site or configure HTTPS for this deployment."
68
+ : "Couldn’t attach this file because secure browser cryptography is unavailable. Attachments require HTTPS and Web Crypto support. Open a secure site in a supported browser or configure HTTPS for this deployment.",
69
+ );
70
+ this.name = "OpenGeniSecureContextRequiredError";
71
+ this.reason = reason;
72
+ }
73
+ }
74
+
52
75
  function decodeApiErrorBody(body: string): {
53
76
  code: string | undefined;
54
77
  message: string | undefined;
@@ -0,0 +1,60 @@
1
+ import type { OpenGeniClient } from "./client";
2
+ import type {
3
+ GitHubRepositoryBranchesResponse,
4
+ ListGitHubRepositoryBranchesOptions,
5
+ VerifyPublicGitHubRepositoryRefRequest,
6
+ VerifyPublicGitHubRepositoryRefResponse,
7
+ } from "./types";
8
+
9
+ type GitHubRepositoryRequestClient = Pick<OpenGeniClient, "requestJson">;
10
+
11
+ /** Verify one exact public github.com repository and branch/tag/SHA anonymously. */
12
+ export async function verifyPublicGitHubRepositoryRef(
13
+ client: GitHubRepositoryRequestClient,
14
+ workspaceId: string,
15
+ request: VerifyPublicGitHubRepositoryRefRequest,
16
+ ): Promise<VerifyPublicGitHubRepositoryRefResponse> {
17
+ return await client.requestJson<VerifyPublicGitHubRepositoryRefResponse>(
18
+ "POST",
19
+ `/v1/workspaces/${workspaceId}/github/public-repositories/verify`,
20
+ request,
21
+ );
22
+ }
23
+
24
+ /** List one bounded page of branch suggestions for one exact workspace App repository. */
25
+ export async function listGitHubRepositoryBranches(
26
+ client: GitHubRepositoryRequestClient,
27
+ workspaceId: string,
28
+ installationId: number,
29
+ repositoryId: number,
30
+ options: ListGitHubRepositoryBranchesOptions = {},
31
+ ): Promise<GitHubRepositoryBranchesResponse> {
32
+ return await client.requestJson<GitHubRepositoryBranchesResponse>(
33
+ "GET",
34
+ `/v1/workspaces/${workspaceId}/github/installations/${installationId}/repositories/${repositoryId}/branches`,
35
+ undefined,
36
+ {
37
+ ...(options.cursor !== undefined ? { cursor: String(options.cursor) } : {}),
38
+ ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
39
+ },
40
+ );
41
+ }
42
+
43
+ /** List one bounded page of branch suggestions for one currently selected personal repository. */
44
+ export async function listPersonalGitHubRepositoryBranches(
45
+ client: GitHubRepositoryRequestClient,
46
+ workspaceId: string,
47
+ connectionId: string,
48
+ repositoryId: string,
49
+ options: ListGitHubRepositoryBranchesOptions = {},
50
+ ): Promise<GitHubRepositoryBranchesResponse> {
51
+ return await client.requestJson<GitHubRepositoryBranchesResponse>(
52
+ "GET",
53
+ `/v1/workspaces/${workspaceId}/connections/${connectionId}/github/repositories/${encodeURIComponent(repositoryId)}/branches`,
54
+ undefined,
55
+ {
56
+ ...(options.cursor !== undefined ? { cursor: String(options.cursor) } : {}),
57
+ ...(options.limit !== undefined ? { limit: String(options.limit) } : {}),
58
+ },
59
+ );
60
+ }
package/src/index.ts CHANGED
@@ -1,6 +1,22 @@
1
1
  export { OpenGeniClient } from "./artifact-client";
2
+ export {
3
+ OpenGeniToolCallError,
4
+ OpenGeniToolReapprovalRequiredError,
5
+ OpenGeniToolsClient,
6
+ } from "./tools";
7
+ export type {
8
+ OpenGeniDynamicToolNamespace,
9
+ OpenGeniDynamicToolNode,
10
+ OpenGeniGeneratedTools,
11
+ OpenGeniToolCallOptions,
12
+ OpenGeniToolFunction,
13
+ OpenGeniToolTransport,
14
+ OpenGeniToolsFacade,
15
+ OpenGeniWorkspaceTools,
16
+ } from "./tools";
2
17
  export type {
3
18
  FetchLike,
19
+ FetchResponse,
4
20
  GetSessionOptions,
5
21
  OpenGeniClientOptions,
6
22
  OpenGeniRequestOptions,
@@ -9,6 +25,43 @@ export type {
9
25
  TranscribeAudioInput,
10
26
  WorkspaceControlEventPage,
11
27
  } from "./client";
28
+ export {
29
+ OPENGENI_SITE_BRIDGE_CANCEL,
30
+ OPENGENI_SITE_BRIDGE_CONNECT,
31
+ OPENGENI_SITE_BRIDGE_READY,
32
+ OPENGENI_SITE_BRIDGE_REQUEST,
33
+ OPENGENI_SITE_BRIDGE_RESPONSE,
34
+ OPENGENI_SITE_BRIDGE_VERSION,
35
+ OpenGeniSiteBridgeError,
36
+ createOpenGeniSiteClient,
37
+ isOpenGeniSiteBridgeCancelMessage,
38
+ isOpenGeniSiteBridgeConnectMessage,
39
+ isOpenGeniSiteBridgeRequestMessage,
40
+ sanitizeOpenGeniSiteToolCallRequest,
41
+ } from "./site";
42
+ export type {
43
+ OpenGeniSiteBridgeCancelMessage,
44
+ OpenGeniSiteBridgeConnectMessage,
45
+ OpenGeniSiteBridgeReadyMessage,
46
+ OpenGeniSiteBridgeRequestMessage,
47
+ OpenGeniSiteBridgeResponseMessage,
48
+ OpenGeniSiteClient,
49
+ OpenGeniSiteClientOptions,
50
+ OpenGeniSiteToolCatalog,
51
+ OpenGeniSiteToolCallRequest,
52
+ OpenGeniSiteWorkspaceTools,
53
+ } from "./site";
54
+ export type {
55
+ ToolGatewayCallRequest,
56
+ ToolGatewayCallResponse,
57
+ ToolGatewayApprovalRequest,
58
+ ToolGatewayApprovalResponse,
59
+ ToolGatewayCatalog,
60
+ ToolGatewayCatalogEntry,
61
+ ToolGatewayDeclarationsResponse,
62
+ ToolGatewayIdentity,
63
+ ToolGatewayResult,
64
+ } from "./types";
12
65
  export type {
13
66
  CreateEditableArtifactMaterializationRequest,
14
67
  CreateEditableArtifactResourceRequest,
@@ -26,16 +79,30 @@ export type {
26
79
  export {
27
80
  OpenGeniApiContractMismatchError,
28
81
  OpenGeniApiError,
82
+ OpenGeniSecureContextRequiredError,
29
83
  OpenGeniSessionListCursorError,
30
84
  OpenGeniStreamError,
31
85
  isRetryableStreamError,
32
86
  } from "./errors";
87
+ export type { OpenGeniSecureContextRequiredReason } from "./errors";
33
88
  export {
34
89
  AUTOMATIC_SESSION_TITLE_FALLBACK,
90
+ deriveAutomaticSessionTitlePreview,
35
91
  deriveSessionDisplayTitle,
36
92
  sessionTitleIsPending,
37
93
  } from "./session-titles";
38
94
  export type { SessionDisplayTitleInput, SessionDisplayTitleOptions } from "./session-titles";
95
+ export type {
96
+ ModelContextInstructionLayer,
97
+ ModelContextInstructionLayerId,
98
+ ModelContextSkill,
99
+ ModelContextSkillKind,
100
+ ModelContextSnapshot,
101
+ ModelContextTokenCounts,
102
+ ModelContextTool,
103
+ ModelContextToolVisibility,
104
+ SessionModelContextResponse,
105
+ } from "./model-context";
39
106
  export {
40
107
  formatSseEvent,
41
108
  proxySessionEventStream,
@@ -134,7 +201,7 @@ export {
134
201
  TtydClientCommand,
135
202
  TtydServerCommand,
136
203
  } from "./terminal";
137
- export { streamSessionEvents } from "./stream";
204
+ export { sessionEventStreamCoveredThrough, streamSessionEvents } from "./stream";
138
205
  export type {
139
206
  SessionEventStreamTransport,
140
207
  StreamConnectionState,
@@ -154,6 +221,7 @@ export type {
154
221
  CreateWorkspaceArtifactRequest,
155
222
  PublishWorkspaceArtifactVersionRequest,
156
223
  RollbackWorkspaceArtifactRequest,
224
+ SetWorkspaceArtifactStatusRequest,
157
225
  WorkspaceArtifact,
158
226
  WorkspaceArtifactContentResponse,
159
227
  WorkspaceArtifactDetailResponse,
@@ -161,6 +229,8 @@ export type {
161
229
  WorkspaceArtifactListOptions,
162
230
  WorkspaceArtifactListResponse,
163
231
  WorkspaceArtifactMutationResponse,
232
+ WorkspaceArtifactSourceBundle,
233
+ WorkspaceArtifactSourceFile,
164
234
  WorkspaceArtifactVersion,
165
235
  } from "./workspace-artifacts";
166
236
  export {
@@ -478,6 +548,7 @@ export type {
478
548
  EndSessionRealtimeRequest,
479
549
  ModelAvailabilityV1,
480
550
  ModelBillingAttributionV1,
551
+ ModelCostClassV1,
481
552
  ModelCapabilitiesV1,
482
553
  ModelCapabilityStateV1,
483
554
  ModelCapabilitySupportV1,
@@ -493,6 +564,22 @@ export type {
493
564
  SessionRealtimeState,
494
565
  WorkspaceModelCatalogModel,
495
566
  WorkspaceModelCatalogResponse,
567
+ WorkspaceGatewayCustomModel,
568
+ WorkspaceGatewayCustomModelsResponse,
569
+ CreateWorkspaceGatewayCustomModelRequest,
570
+ DeleteWorkspaceGatewayCustomModelRequest,
571
+ WorkspaceOpenRouterCustomModel,
572
+ WorkspaceOpenRouterCustomModelsResponse,
573
+ CreateWorkspaceOpenRouterCustomModelRequest,
574
+ DeleteWorkspaceOpenRouterCustomModelRequest,
575
+ OrganizationModelProviderKind,
576
+ OrganizationModelProviderConnection,
577
+ UpsertOrganizationModelProviderConnectionRequest,
578
+ RevokeOrganizationModelProviderConnectionRequest,
579
+ OrganizationProviderCustomModel,
580
+ OrganizationProviderCustomModelsResponse,
581
+ CreateOrganizationProviderCustomModelRequest,
582
+ DeleteOrganizationProviderCustomModelRequest,
496
583
  WorkspaceModelAccessPolicy,
497
584
  WorkspaceRealtimeModelCatalogItem,
498
585
  WorkspaceRealtimeModelCatalogResponse,
@@ -542,6 +629,7 @@ export type {
542
629
  PersonalGitHubDisconnectRequest,
543
630
  PersonalGitHubOAuthStartRequest,
544
631
  PersonalGitHubOAuthStartResponse,
632
+ ListGitHubRepositoryBranchesOptions,
545
633
  ListPersonalGitHubRepositoriesOptions,
546
634
  ListPersonalGitHubRepositoriesResponse,
547
635
  PersonalGitHubRepository,
@@ -644,6 +732,10 @@ export type {
644
732
  GitHubBindingStatus,
645
733
  GitHubInstallationBinding,
646
734
  GitHubInstallationLifecycle,
735
+ GitHubRepositoryBranch,
736
+ GitHubRepositoryBranchesResponse,
737
+ VerifyPublicGitHubRepositoryRefRequest,
738
+ VerifyPublicGitHubRepositoryRefResponse,
647
739
  GitHubRepositoriesResponse,
648
740
  GoogleDriveBrowseItem,
649
741
  GoogleDriveBrowseResponse,
@@ -894,6 +986,7 @@ export type {
894
986
  FsNodeType,
895
987
  FsTreeNode,
896
988
  FsEncoding,
989
+ FileSystemRouteIdentity,
897
990
  FsListRequest,
898
991
  FsListResponse,
899
992
  FsListBatchRequest,
@@ -0,0 +1,69 @@
1
+ export type ModelContextInstructionLayerId =
2
+ | "operational_contract"
3
+ | "persona_and_core"
4
+ | "workspace_governance"
5
+ | "session_instructions"
6
+ | "workspace_memory"
7
+ | "codemode"
8
+ | "git_bindings"
9
+ | "genesis_title"
10
+ | "sdk_capability_instructions"
11
+ | "sandbox_preamble"
12
+ | "sandbox_filesystem"
13
+ | "sent_system_instructions";
14
+
15
+ export type ModelContextInstructionLayer = {
16
+ id: ModelContextInstructionLayerId;
17
+ title: string;
18
+ content: string;
19
+ utf8Bytes: number;
20
+ estimatedTokens: number;
21
+ };
22
+
23
+ export type ModelContextToolVisibility = "eager" | "searchable";
24
+
25
+ export type ModelContextTool = {
26
+ name: string;
27
+ type: string;
28
+ visibility: ModelContextToolVisibility;
29
+ description?: string | undefined;
30
+ namespace?: string | undefined;
31
+ schema?: unknown | undefined;
32
+ utf8Bytes: number;
33
+ estimatedTokens: number;
34
+ };
35
+
36
+ export type ModelContextSkillKind = "preference_descriptor" | "runtime_skill" | "native_tool_skill";
37
+
38
+ export type ModelContextSkill = {
39
+ kind: ModelContextSkillKind;
40
+ name: string;
41
+ description: string;
42
+ source?: string | undefined;
43
+ path?: string | undefined;
44
+ };
45
+
46
+ export type ModelContextTokenCounts = {
47
+ instructions: number;
48
+ tools: number;
49
+ prefix: number;
50
+ };
51
+
52
+ export type ModelContextSnapshot = {
53
+ version: 1;
54
+ capturedAt: string;
55
+ source: "model_request";
56
+ requestIndex: number;
57
+ instructions: string;
58
+ layers: ModelContextInstructionLayer[];
59
+ tools: ModelContextTool[];
60
+ skills: ModelContextSkill[];
61
+ tokens: ModelContextTokenCounts;
62
+ };
63
+
64
+ export type SessionModelContextResponse = {
65
+ sessionId: string;
66
+ attemptId: string | null;
67
+ turnId: string | null;
68
+ snapshot: ModelContextSnapshot | null;
69
+ };
@@ -0,0 +1,6 @@
1
+ export {
2
+ compareModelPickerOrder,
3
+ modelPickerBillingClassFor,
4
+ type ModelPickerBillingCandidate,
5
+ type ModelPickerBillingClass,
6
+ } from "@opengeni/contracts/model-picker-order";
package/src/proxy.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { OpenGeniClient } from "./client";
2
- import type { StreamSessionEventsOptions } from "./stream";
2
+ import { sessionEventStreamCoveredThrough, type StreamSessionEventsOptions } from "./stream";
3
3
  import type { SessionEvent } from "./types";
4
4
 
5
5
  /**
@@ -12,12 +12,22 @@ import type { SessionEvent } from "./types";
12
12
  * OpenGeni's own SSE stream (`id: <sequence>`, `event: <type>`,
13
13
  * `data: <event JSON>`), so the browser side can consume it with this same
14
14
  * SDK's streaming core (or a plain `EventSource`), including resume via
15
- * `?after=` / `Last-Event-ID`.
15
+ * `?after=` / `Last-Event-ID`. An ordinary event uses its own sequence as the
16
+ * SSE `id`; a compact event uses the server-owned sequence it covers through.
16
17
  */
17
18
 
18
- /** Format one event exactly as OpenGeni's API emits it over SSE. */
19
+ /** Format one event with the exact OpenGeni data and resume-cursor semantics. */
19
20
  export function formatSseEvent(event: SessionEvent): string {
20
- return `id: ${event.sequence}\nevent: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`;
21
+ const streamedCoverage = sessionEventStreamCoveredThrough(event);
22
+ const projectedCoverage = event.coveredThrough;
23
+ const coveredThrough =
24
+ streamedCoverage ??
25
+ (typeof projectedCoverage === "number" &&
26
+ Number.isSafeInteger(projectedCoverage) &&
27
+ projectedCoverage >= event.sequence
28
+ ? projectedCoverage
29
+ : event.sequence);
30
+ return `id: ${coveredThrough}\nevent: ${event.type}\ndata: ${JSON.stringify(event)}\n\n`;
21
31
  }
22
32
 
23
33
  export type SseReStreamOptions = {
@@ -115,8 +125,9 @@ export function sessionEventsToSseResponse(
115
125
 
116
126
  /**
117
127
  * Read the resume cursor a reconnecting SSE client sent: the `after` query
118
- * parameter, or the standard `Last-Event-ID` header (the re-emitted stream
119
- * sets `id:` to the sequence). Returns 0 (full replay) when absent.
128
+ * parameter, or the standard `Last-Event-ID` header. Re-emitted streams set
129
+ * `id:` to the event sequence for ordinary events and the covered high-water
130
+ * sequence for compact events. Returns 0 (full replay) when absent.
120
131
  */
121
132
  export function resumeSequenceFromRequest(request: Request): number {
122
133
  const url = new URL(request.url);
@@ -1,84 +1,10 @@
1
- import {
1
+ export {
2
2
  AUTOMATIC_SESSION_TITLE_FALLBACK,
3
- boundAutomaticSessionTitle,
4
- containsSensitiveAutomaticSessionTitleValue,
3
+ deriveAutomaticSessionTitlePreview,
4
+ deriveSessionDisplayTitle,
5
+ sessionTitleIsPending,
6
+ } from "@opengeni/contracts/session-titles";
7
+ export type {
8
+ SessionDisplayTitleInput,
9
+ SessionDisplayTitleOptions,
5
10
  } from "@opengeni/contracts/session-titles";
6
- import type { Session } from "./types";
7
-
8
- export { AUTOMATIC_SESSION_TITLE_FALLBACK };
9
-
10
- // Session creation accepts a body far larger than a navigation label. Bound
11
- // the source before any replace/split/normalization so a persisted large prompt
12
- // cannot amplify memory or CPU on every browser render.
13
- const PROMPT_PREVIEW_SCAN_MAX_CODE_UNITS = 4_096;
14
-
15
- function deriveOpeningPromptPreview(value: unknown): string | null {
16
- if (typeof value !== "string") return null;
17
-
18
- const firstLine = value
19
- .slice(0, PROMPT_PREVIEW_SCAN_MAX_CODE_UNITS)
20
- .replace(/[\u0000-\u001f\u007f-\u009f]+/gu, "\n")
21
- .split(/\n+/u)
22
- .map((line) => line.trim())
23
- .find(Boolean);
24
- if (!firstLine) return null;
25
- if (containsSensitiveAutomaticSessionTitleValue(firstLine)) return null;
26
-
27
- const preview = boundAutomaticSessionTitle(firstLine.replace(/\s+/gu, " "))
28
- .replace(/[\s.!?,;:\-–—]+$/u, "")
29
- .trim();
30
- if (!preview) return null;
31
- return preview;
32
- }
33
-
34
- export type SessionDisplayTitleInput = {
35
- title?: Session["title"] | undefined;
36
- titleSource?: Session["titleSource"] | undefined;
37
- initialMessage?: Session["initialMessage"] | null | undefined;
38
- metadata?: Readonly<Record<string, unknown>> | undefined;
39
- };
40
-
41
- export type SessionDisplayTitleOptions = {
42
- /** Optional metadata fields to try before the opening-prompt preview. */
43
- metadataKeys?: readonly string[] | undefined;
44
- };
45
-
46
- /**
47
- * Whether the durable title still represents the automatic-title pending state.
48
- * A user-authored title always wins, even when its literal value is the fallback.
49
- */
50
- export function sessionTitleIsPending(input: SessionDisplayTitleInput): boolean {
51
- const title = input.title?.trim() ?? "";
52
- return input.titleSource !== "user" && (!title || title === AUTOMATIC_SESSION_TITLE_FALLBACK);
53
- }
54
-
55
- /**
56
- * Derive the title a client should display for a session.
57
- *
58
- * A semantic agent title or human rename wins. While automatic naming is still
59
- * pending, clients show a short, sensitive-safe preview of the opening prompt;
60
- * the preview is never persisted as title metadata and is replaced naturally
61
- * when `session.title_set` arrives. Obvious credential-, URL-, or identifier-
62
- * shaped prompt prefixes retain the generic fallback.
63
- */
64
- export function deriveSessionDisplayTitle(
65
- input: SessionDisplayTitleInput,
66
- options: SessionDisplayTitleOptions = {},
67
- ): string {
68
- const title = input.title?.trim() ?? "";
69
- if (input.titleSource === "user") {
70
- return title || AUTOMATIC_SESSION_TITLE_FALLBACK;
71
- }
72
- if (title && !sessionTitleIsPending(input)) {
73
- return title;
74
- }
75
-
76
- for (const key of options.metadataKeys ?? []) {
77
- const value = input.metadata?.[key];
78
- if (typeof value === "string" && value.trim().length > 0) {
79
- return value.trim();
80
- }
81
- }
82
-
83
- return deriveOpeningPromptPreview(input.initialMessage) ?? AUTOMATIC_SESSION_TITLE_FALLBACK;
84
- }