@opengeni/contracts 2.2.0-canary.0 → 2.5.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.
- package/dist/agent-authored-durable-text.d.ts +7 -4
- package/dist/atlassian.js +1 -1
- package/dist/{chunk-7ZZ3R27P.js → chunk-4I7DWNRR.js} +585 -37
- package/dist/chunk-4I7DWNRR.js.map +1 -0
- package/dist/company-profile.d.ts +3 -3
- package/dist/connection-authority.js +1 -1
- package/dist/google-drive.js +1 -1
- package/dist/index.d.ts +318 -12
- package/dist/index.js +83 -3
- package/dist/knowledge.d.ts +5 -0
- package/dist/organization-membership-lifecycle.d.ts +493 -14
- package/dist/personal-github.js +1 -1
- package/dist/remember.d.ts +7 -4
- package/dist/session-titles.d.ts +23 -0
- package/package.json +1 -1
- package/src/agent-authored-durable-text.ts +12 -11
- package/src/company-profile.ts +3 -3
- package/src/index.ts +281 -19
- package/src/knowledge.ts +1 -0
- package/src/organization-membership-lifecycle.ts +222 -19
- package/src/remember.ts +6 -4
- package/src/session-titles.ts +324 -0
- package/dist/chunk-7ZZ3R27P.js.map +0 -1
|
@@ -4,12 +4,40 @@ import { Permission } from "./permissions";
|
|
|
4
4
|
export const OrganizationMembershipRole = z.enum(["owner", "admin", "member"]);
|
|
5
5
|
export type OrganizationMembershipRole = z.infer<typeof OrganizationMembershipRole>;
|
|
6
6
|
|
|
7
|
+
export const WorkspaceMemberRole = z.enum(["viewer", "member", "admin", "custom"]);
|
|
8
|
+
export type WorkspaceMemberRole = z.infer<typeof WorkspaceMemberRole>;
|
|
9
|
+
|
|
10
|
+
export const AssignableWorkspaceMemberRole = z.enum(["viewer", "member", "admin"]);
|
|
11
|
+
export type AssignableWorkspaceMemberRole = z.infer<typeof AssignableWorkspaceMemberRole>;
|
|
12
|
+
|
|
7
13
|
export const OrganizationInvitationStatus = z.enum(["pending", "accepted", "revoked", "expired"]);
|
|
8
14
|
export type OrganizationInvitationStatus = z.infer<typeof OrganizationInvitationStatus>;
|
|
9
15
|
|
|
16
|
+
export const OrganizationUserSetupDeliveryState = z.enum([
|
|
17
|
+
"pending",
|
|
18
|
+
"sent",
|
|
19
|
+
"failed",
|
|
20
|
+
"outcome_unknown",
|
|
21
|
+
"revoked",
|
|
22
|
+
]);
|
|
23
|
+
export type OrganizationUserSetupDeliveryState = z.infer<typeof OrganizationUserSetupDeliveryState>;
|
|
24
|
+
|
|
25
|
+
export const OrganizationUserSetupDelivery = z.object({
|
|
26
|
+
id: z.string().uuid(),
|
|
27
|
+
state: OrganizationUserSetupDeliveryState,
|
|
28
|
+
attemptCount: z.number().int().nonnegative(),
|
|
29
|
+
revision: z.number().int().positive(),
|
|
30
|
+
errorClass: z.string().min(1).max(64).nullable(),
|
|
31
|
+
retryState: z.enum(["available", "reconciliation_required", "unavailable"]),
|
|
32
|
+
sentAt: z.string().datetime({ offset: true }).nullable(),
|
|
33
|
+
updatedAt: z.string().datetime({ offset: true }),
|
|
34
|
+
});
|
|
35
|
+
export type OrganizationUserSetupDelivery = z.infer<typeof OrganizationUserSetupDelivery>;
|
|
36
|
+
|
|
10
37
|
export const OrganizationInvitation = z.object({
|
|
11
38
|
id: z.string().uuid(),
|
|
12
39
|
organizationId: z.string().uuid(),
|
|
40
|
+
organizationName: z.string().trim().min(1).max(120).nullable().default(null),
|
|
13
41
|
targetEmail: z.string().email(),
|
|
14
42
|
targetName: z.string().trim().min(1).max(120).nullable().default(null),
|
|
15
43
|
initialWorkspaceIds: z.array(z.string().uuid()).max(100).default([]),
|
|
@@ -20,6 +48,7 @@ export const OrganizationInvitation = z.object({
|
|
|
20
48
|
acceptedMembershipId: z.string().uuid().nullable(),
|
|
21
49
|
createdAt: z.string().datetime({ offset: true }),
|
|
22
50
|
updatedAt: z.string().datetime({ offset: true }),
|
|
51
|
+
delivery: OrganizationUserSetupDelivery.nullable().default(null),
|
|
23
52
|
});
|
|
24
53
|
export type OrganizationInvitation = z.infer<typeof OrganizationInvitation>;
|
|
25
54
|
|
|
@@ -40,6 +69,34 @@ export const OrganizationMember = z.object({
|
|
|
40
69
|
});
|
|
41
70
|
export type OrganizationMember = z.infer<typeof OrganizationMember>;
|
|
42
71
|
|
|
72
|
+
export const OrganizationAdministrationMemberWorkspaceAccess = z.object({
|
|
73
|
+
workspaceId: z.string().uuid(),
|
|
74
|
+
workspaceName: z.string().min(1).max(255),
|
|
75
|
+
membershipId: z.string().uuid(),
|
|
76
|
+
role: WorkspaceMemberRole,
|
|
77
|
+
updatedAt: z.string().datetime({ offset: true }),
|
|
78
|
+
});
|
|
79
|
+
export type OrganizationAdministrationMemberWorkspaceAccess = z.infer<
|
|
80
|
+
typeof OrganizationAdministrationMemberWorkspaceAccess
|
|
81
|
+
>;
|
|
82
|
+
|
|
83
|
+
/** Safe admin projection. Personal-workspace and personal-resource metadata are absent. */
|
|
84
|
+
export const OrganizationAdministrationMember = z.object({
|
|
85
|
+
id: z.string().uuid(),
|
|
86
|
+
organizationId: z.string().uuid(),
|
|
87
|
+
subjectId: z.string().min(1).max(1024),
|
|
88
|
+
name: z.string().min(1).max(1024).nullable(),
|
|
89
|
+
email: z.string().email().max(320).nullable(),
|
|
90
|
+
role: OrganizationMembershipRole,
|
|
91
|
+
status: z.enum(["provisioning", "active", "suspended", "revoked"]),
|
|
92
|
+
authorizationRevision: z.number().int().positive(),
|
|
93
|
+
sharedWorkspaceAccess: z.array(OrganizationAdministrationMemberWorkspaceAccess).max(500),
|
|
94
|
+
revokedAt: z.string().datetime({ offset: true }).nullable(),
|
|
95
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
96
|
+
updatedAt: z.string().datetime({ offset: true }),
|
|
97
|
+
});
|
|
98
|
+
export type OrganizationAdministrationMember = z.infer<typeof OrganizationAdministrationMember>;
|
|
99
|
+
|
|
43
100
|
export const OrganizationSummary = z.object({
|
|
44
101
|
id: z.string().uuid(),
|
|
45
102
|
name: z.string().trim().min(1).max(120),
|
|
@@ -71,12 +128,17 @@ export type UpdateOrganizationPrivateSessionSettingsRequest = z.infer<
|
|
|
71
128
|
|
|
72
129
|
export const OrganizationWorkspaceAccessMember = z.object({
|
|
73
130
|
membershipId: z.string().uuid(),
|
|
131
|
+
organizationMembershipId: z.string().uuid().nullable(),
|
|
74
132
|
subjectId: z.string().min(1).max(1024),
|
|
133
|
+
name: z.string().min(1).max(1024).nullable(),
|
|
134
|
+
email: z.string().email().max(320).nullable(),
|
|
75
135
|
subjectLabel: z.string().min(1).max(1024).nullable(),
|
|
76
136
|
principalKind: z.enum(["human", "service"]),
|
|
77
|
-
|
|
137
|
+
organizationRole: OrganizationMembershipRole.nullable(),
|
|
138
|
+
role: WorkspaceMemberRole,
|
|
78
139
|
permissions: z.array(z.string().min(1).max(128)),
|
|
79
140
|
createdAt: z.string().datetime({ offset: true }),
|
|
141
|
+
updatedAt: z.string().datetime({ offset: true }),
|
|
80
142
|
});
|
|
81
143
|
export type OrganizationWorkspaceAccessMember = z.infer<typeof OrganizationWorkspaceAccessMember>;
|
|
82
144
|
|
|
@@ -90,32 +152,80 @@ export const OrganizationWorkspaceAccess = z.object({
|
|
|
90
152
|
});
|
|
91
153
|
export type OrganizationWorkspaceAccess = z.infer<typeof OrganizationWorkspaceAccess>;
|
|
92
154
|
|
|
155
|
+
export const OrganizationWorkspaceRoleDefinition = z.object({
|
|
156
|
+
role: AssignableWorkspaceMemberRole,
|
|
157
|
+
label: z.string().min(1).max(64),
|
|
158
|
+
description: z.string().min(1).max(512),
|
|
159
|
+
permissions: z.array(Permission).min(1),
|
|
160
|
+
});
|
|
161
|
+
export type OrganizationWorkspaceRoleDefinition = z.infer<
|
|
162
|
+
typeof OrganizationWorkspaceRoleDefinition
|
|
163
|
+
>;
|
|
164
|
+
|
|
93
165
|
export const OrganizationAdministrationOverview = z.object({
|
|
94
166
|
organization: OrganizationSummary,
|
|
167
|
+
roles: z.array(OrganizationWorkspaceRoleDefinition).length(3),
|
|
95
168
|
workspaces: z.array(OrganizationWorkspaceAccess).max(500),
|
|
96
169
|
});
|
|
97
170
|
export type OrganizationAdministrationOverview = z.infer<typeof OrganizationAdministrationOverview>;
|
|
98
171
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
172
|
+
export const CreateOrganizationWorkspaceRequest = z
|
|
173
|
+
.object({
|
|
174
|
+
name: z.string().trim().min(1).max(120),
|
|
175
|
+
operationId: z.string().uuid(),
|
|
176
|
+
})
|
|
177
|
+
.strict();
|
|
178
|
+
export type CreateOrganizationWorkspaceRequest = z.infer<typeof CreateOrganizationWorkspaceRequest>;
|
|
179
|
+
|
|
180
|
+
export const UpdateOrganizationWorkspaceRequest = z
|
|
181
|
+
.object({
|
|
182
|
+
name: z.string().trim().min(1).max(120),
|
|
183
|
+
expectedUpdatedAt: z.string().datetime({ offset: true }),
|
|
184
|
+
operationId: z.string().uuid(),
|
|
185
|
+
})
|
|
186
|
+
.strict();
|
|
187
|
+
export type UpdateOrganizationWorkspaceRequest = z.infer<typeof UpdateOrganizationWorkspaceRequest>;
|
|
188
|
+
|
|
189
|
+
export const PutOrganizationWorkspaceMemberRequest = z.discriminatedUnion("role", [
|
|
190
|
+
z
|
|
191
|
+
.object({
|
|
192
|
+
role: AssignableWorkspaceMemberRole,
|
|
193
|
+
expectedUpdatedAt: z.string().datetime({ offset: true }).nullable(),
|
|
194
|
+
operationId: z.string().uuid(),
|
|
195
|
+
})
|
|
196
|
+
.strict(),
|
|
197
|
+
z
|
|
198
|
+
.object({
|
|
199
|
+
role: z.literal("custom"),
|
|
200
|
+
permissions: z.array(Permission).max(128),
|
|
201
|
+
expectedUpdatedAt: z.string().datetime({ offset: true }).nullable(),
|
|
202
|
+
operationId: z.string().uuid(),
|
|
203
|
+
})
|
|
204
|
+
.strict(),
|
|
205
|
+
]);
|
|
206
|
+
export type PutOrganizationWorkspaceMemberRequest = z.infer<
|
|
207
|
+
typeof PutOrganizationWorkspaceMemberRequest
|
|
110
208
|
>;
|
|
111
209
|
|
|
112
|
-
export const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
export type
|
|
210
|
+
export const RevokeOrganizationWorkspaceMemberRequest = z
|
|
211
|
+
.object({
|
|
212
|
+
expectedUpdatedAt: z.string().datetime({ offset: true }),
|
|
213
|
+
operationId: z.string().uuid(),
|
|
214
|
+
})
|
|
215
|
+
.strict();
|
|
216
|
+
export type RevokeOrganizationWorkspaceMemberRequest = z.infer<
|
|
217
|
+
typeof RevokeOrganizationWorkspaceMemberRequest
|
|
218
|
+
>;
|
|
219
|
+
|
|
220
|
+
export const RevokeOrganizationWorkspaceMemberResponse = z
|
|
221
|
+
.object({
|
|
222
|
+
removed: z.boolean(),
|
|
223
|
+
replay: z.boolean(),
|
|
224
|
+
})
|
|
225
|
+
.strict();
|
|
226
|
+
export type RevokeOrganizationWorkspaceMemberResponse = z.infer<
|
|
227
|
+
typeof RevokeOrganizationWorkspaceMemberResponse
|
|
228
|
+
>;
|
|
119
229
|
|
|
120
230
|
export const CreateOrganizationRequest = z.object({
|
|
121
231
|
name: z.string().trim().min(1).max(120),
|
|
@@ -238,6 +348,96 @@ export type CreateOrganizationInvitationRequest = z.infer<
|
|
|
238
348
|
typeof CreateOrganizationInvitationRequest
|
|
239
349
|
>;
|
|
240
350
|
|
|
351
|
+
/**
|
|
352
|
+
* Public, signed-out completion contract for an invitation-bound account setup.
|
|
353
|
+
* The bearer is delivered out of band to the invited email address. It is
|
|
354
|
+
* never persisted in plaintext; `operationId` makes an outcome-unknown retry
|
|
355
|
+
* converge on the first committed result.
|
|
356
|
+
*/
|
|
357
|
+
export const CompleteOrganizationUserSetupRequest = z.object({
|
|
358
|
+
token: z.string().min(32).max(2048),
|
|
359
|
+
name: z.string().trim().min(1).max(120),
|
|
360
|
+
password: z.string().min(8).max(128),
|
|
361
|
+
operationId: z.string().uuid(),
|
|
362
|
+
});
|
|
363
|
+
export type CompleteOrganizationUserSetupRequest = z.infer<
|
|
364
|
+
typeof CompleteOrganizationUserSetupRequest
|
|
365
|
+
>;
|
|
366
|
+
|
|
367
|
+
export const CompleteOrganizationUserSetupResponse = z.object({
|
|
368
|
+
status: z.literal("complete"),
|
|
369
|
+
});
|
|
370
|
+
export type CompleteOrganizationUserSetupResponse = z.infer<
|
|
371
|
+
typeof CompleteOrganizationUserSetupResponse
|
|
372
|
+
>;
|
|
373
|
+
|
|
374
|
+
export const PreviewOrganizationUserSetupRequest = z
|
|
375
|
+
.object({ token: z.string().min(32).max(2048) })
|
|
376
|
+
.strict();
|
|
377
|
+
export type PreviewOrganizationUserSetupRequest = z.infer<
|
|
378
|
+
typeof PreviewOrganizationUserSetupRequest
|
|
379
|
+
>;
|
|
380
|
+
|
|
381
|
+
export const OrganizationUserSetupPreviewWorkspace = z.object({
|
|
382
|
+
workspaceId: z.string().uuid(),
|
|
383
|
+
workspaceName: z.string().min(1).max(255),
|
|
384
|
+
role: AssignableWorkspaceMemberRole,
|
|
385
|
+
});
|
|
386
|
+
export type OrganizationUserSetupPreviewWorkspace = z.infer<
|
|
387
|
+
typeof OrganizationUserSetupPreviewWorkspace
|
|
388
|
+
>;
|
|
389
|
+
|
|
390
|
+
const OrganizationUserSetupPreviewUnavailable = z.object({
|
|
391
|
+
state: z.enum(["unavailable", "expired", "revoked", "completed"]),
|
|
392
|
+
});
|
|
393
|
+
const OrganizationUserSetupPreviewPending = z.object({
|
|
394
|
+
state: z.literal("pending"),
|
|
395
|
+
organizationId: z.string().uuid(),
|
|
396
|
+
organizationName: z.string().trim().min(1).max(120),
|
|
397
|
+
targetEmail: z.string().email().max(320),
|
|
398
|
+
targetName: z.string().trim().min(1).max(120).nullable(),
|
|
399
|
+
organizationRole: OrganizationMembershipRole,
|
|
400
|
+
sharedWorkspaceAccess: z.array(OrganizationUserSetupPreviewWorkspace).max(100),
|
|
401
|
+
expiresAt: z.string().datetime({ offset: true }),
|
|
402
|
+
});
|
|
403
|
+
export const OrganizationUserSetupPreview = z.discriminatedUnion("state", [
|
|
404
|
+
OrganizationUserSetupPreviewUnavailable,
|
|
405
|
+
OrganizationUserSetupPreviewPending,
|
|
406
|
+
]);
|
|
407
|
+
export type OrganizationUserSetupPreview = z.infer<typeof OrganizationUserSetupPreview>;
|
|
408
|
+
|
|
409
|
+
export const RetryOrganizationUserSetupDeliveryRequest = z
|
|
410
|
+
.object({ operationId: z.string().uuid() })
|
|
411
|
+
.strict();
|
|
412
|
+
export type RetryOrganizationUserSetupDeliveryRequest = z.infer<
|
|
413
|
+
typeof RetryOrganizationUserSetupDeliveryRequest
|
|
414
|
+
>;
|
|
415
|
+
|
|
416
|
+
/** Authenticated organization-name-only setup after ordinary account creation. */
|
|
417
|
+
export const CompleteSelfServiceOrganizationSetupRequest = z.object({
|
|
418
|
+
organizationName: z.string().trim().min(1).max(120),
|
|
419
|
+
operationId: z.string().uuid(),
|
|
420
|
+
});
|
|
421
|
+
export type CompleteSelfServiceOrganizationSetupRequest = z.infer<
|
|
422
|
+
typeof CompleteSelfServiceOrganizationSetupRequest
|
|
423
|
+
>;
|
|
424
|
+
|
|
425
|
+
export const CompleteSelfServiceOrganizationSetupResponse = z.object({
|
|
426
|
+
status: z.literal("complete"),
|
|
427
|
+
organizationId: z.string().uuid(),
|
|
428
|
+
personalWorkspaceId: z.string().uuid(),
|
|
429
|
+
});
|
|
430
|
+
export type CompleteSelfServiceOrganizationSetupResponse = z.infer<
|
|
431
|
+
typeof CompleteSelfServiceOrganizationSetupResponse
|
|
432
|
+
>;
|
|
433
|
+
|
|
434
|
+
export const SelfServiceOrganizationOnboardingStatus = z.object({
|
|
435
|
+
state: z.enum(["required", "invitation_pending", "unavailable", "complete"]),
|
|
436
|
+
});
|
|
437
|
+
export type SelfServiceOrganizationOnboardingStatus = z.infer<
|
|
438
|
+
typeof SelfServiceOrganizationOnboardingStatus
|
|
439
|
+
>;
|
|
440
|
+
|
|
241
441
|
export const AcceptOrganizationInvitationRequest = z.object({
|
|
242
442
|
expectedRevision: z.number().int().positive(),
|
|
243
443
|
operationId: z.string().uuid(),
|
|
@@ -325,6 +525,9 @@ export type AcceptOrganizationInvitationResponse = z.infer<
|
|
|
325
525
|
export const ListOrganizationMembersResponse = z.object({
|
|
326
526
|
members: z.array(OrganizationMember),
|
|
327
527
|
});
|
|
528
|
+
export const ListOrganizationAdministrationMembersResponse = z.object({
|
|
529
|
+
members: z.array(OrganizationAdministrationMember).max(1000),
|
|
530
|
+
});
|
|
328
531
|
export const ListSelfOrganizationMembershipsResponse = z.object({
|
|
329
532
|
memberships: z.array(OrganizationMember),
|
|
330
533
|
});
|
package/src/remember.ts
CHANGED
|
@@ -21,7 +21,7 @@ import { WorkspaceInstructionPolicyTarget } from "./workspace-instruction-polici
|
|
|
21
21
|
/**
|
|
22
22
|
* Explicit user-directed durable write ("remember X for this workspace").
|
|
23
23
|
*
|
|
24
|
-
* The lane is the
|
|
24
|
+
* The lane is the Agent Knowledge area the content belongs to:
|
|
25
25
|
* - `preference`: a Ways-of-working preference (how agents should act);
|
|
26
26
|
* - `instruction_policy`: a mandatory workspace rule ("always"/"never");
|
|
27
27
|
* - `knowledge`: a company/product/people fact.
|
|
@@ -84,7 +84,7 @@ export const RememberRequest = z.discriminatedUnion("lane", [
|
|
|
84
84
|
lane: z.literal("instruction_policy"),
|
|
85
85
|
// A mandatory rule is composed verbatim into the prompt of every session
|
|
86
86
|
// it applies to, for as long as it stays active, so this is the tightest
|
|
87
|
-
// agent budget in
|
|
87
|
+
// agent budget in Agent Knowledge.
|
|
88
88
|
content: z
|
|
89
89
|
.string()
|
|
90
90
|
.trim()
|
|
@@ -145,11 +145,13 @@ export const RememberActivationSummary = z.discriminatedUnion("destination", [
|
|
|
145
145
|
destination: z.literal("knowledge"),
|
|
146
146
|
receiptId: z.string().uuid(),
|
|
147
147
|
claimId: z.string().uuid(),
|
|
148
|
+
/** The retrievable Memory record materialized from the approved claim. */
|
|
149
|
+
memoryId: z.string().uuid(),
|
|
148
150
|
approvalReviewId: z.string().uuid(),
|
|
149
151
|
effectiveAt: z.string().datetime().nullable(),
|
|
150
152
|
authorityKind: z.literal("human_confirmed"),
|
|
151
|
-
/**
|
|
152
|
-
undo: z.literal("
|
|
153
|
+
/** Correct or archive the materialized record through the Memory lifecycle. */
|
|
154
|
+
undo: z.literal("memory_management"),
|
|
153
155
|
}),
|
|
154
156
|
]);
|
|
155
157
|
export type RememberActivationSummary = z.infer<typeof RememberActivationSummary>;
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/** Human-authored session titles keep the existing public API ceiling. */
|
|
2
|
+
export const SESSION_TITLE_MAX_CHARACTERS = 200;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Automatic titles are intentionally much shorter than the manual rename
|
|
6
|
+
* ceiling. This is measured in grapheme clusters so emoji and combining text
|
|
7
|
+
* are never split into malformed display strings.
|
|
8
|
+
*/
|
|
9
|
+
export const AUTOMATIC_SESSION_TITLE_MAX_GRAPHEMES = 80;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Safe durable title used until semantic generation succeeds. It contains no
|
|
13
|
+
* user prompt bytes, so provider failure/offline operation cannot leak a
|
|
14
|
+
* credential or leave a raw prompt prefix in navigation surfaces.
|
|
15
|
+
*/
|
|
16
|
+
export const AUTOMATIC_SESSION_TITLE_FALLBACK = "New conversation";
|
|
17
|
+
|
|
18
|
+
let titleSegmenter: Intl.Segmenter | null | undefined;
|
|
19
|
+
|
|
20
|
+
const KNOWN_SENSITIVE_VALUE_PATTERNS = [
|
|
21
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/iu,
|
|
22
|
+
/\bBearer\s+[A-Za-z0-9._~+/=-]{8,}/iu,
|
|
23
|
+
/\b(?:sk-(?:proj-)?|gh[oprsu]_|github_pat_|glpat-|xox[baprs]-)[A-Za-z0-9_-]{8,}/iu,
|
|
24
|
+
/\bAKIA[0-9A-Z]{16}\b/u,
|
|
25
|
+
/\bAIza[0-9A-Za-z_-]{20,}\b/u,
|
|
26
|
+
/\beyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/u,
|
|
27
|
+
/[?&](?:access_token|api_key|apikey|password|secret|token)=[^\s&#]+/iu,
|
|
28
|
+
] as const;
|
|
29
|
+
|
|
30
|
+
const URI_SCHEME_CANDIDATE_PATTERN = /\b[a-z][a-z0-9+.-]*:\S+/giu;
|
|
31
|
+
const WINDOWS_DRIVE_PATH_PATTERN = /^[a-z]:[\\/](?![\\/])[^:]*$/iu;
|
|
32
|
+
|
|
33
|
+
const SCHEMELESS_HOST_CANDIDATE_PATTERN =
|
|
34
|
+
/\b(?:www\.)?(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?(?::\d{1,5})?(?:[/?#][^\s]*)?/giu;
|
|
35
|
+
|
|
36
|
+
const SCHEMELESS_LOCAL_NETWORK_PATTERNS = [
|
|
37
|
+
/\blocalhost(?:(?::\d{1,5})(?:[/?#][^\s]*)?|[/?#][^\s]*)/iu,
|
|
38
|
+
/\b(?:(?:25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(?:25[0-5]|2[0-4]\d|1?\d?\d)(?:(?::\d{1,5})(?:[/?#][^\s]*)?|[/?#][^\s]*)/u,
|
|
39
|
+
/\[(?=[0-9a-f:.]*:[0-9a-f:.]*\])[0-9a-f:.]+\](?:(?::\d{1,5})(?:[/?#][^\s]*)?|[/?#][^\s]*)/iu,
|
|
40
|
+
] as const;
|
|
41
|
+
|
|
42
|
+
const FILE_LIKE_HOST_SUFFIXES = new Set([
|
|
43
|
+
"css",
|
|
44
|
+
"html",
|
|
45
|
+
"js",
|
|
46
|
+
"json",
|
|
47
|
+
"jsx",
|
|
48
|
+
"lock",
|
|
49
|
+
"md",
|
|
50
|
+
"sql",
|
|
51
|
+
"toml",
|
|
52
|
+
"ts",
|
|
53
|
+
"tsx",
|
|
54
|
+
"xml",
|
|
55
|
+
"yaml",
|
|
56
|
+
"yml",
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
// These authorities are established framework/namespace notation whose exact
|
|
60
|
+
// casing is meaningful. Keep this exception deliberately narrow: generic
|
|
61
|
+
// PascalCase or uppercase authorities remain host-like because an uppercase
|
|
62
|
+
// real domain (for example MICROSOFT.COM/Admin) must not bypass the URL gate.
|
|
63
|
+
const DOTTED_TECHNOLOGY_PATH_AUTHORITIES = new Set([
|
|
64
|
+
"ASP.NET",
|
|
65
|
+
"AWS.SDK",
|
|
66
|
+
"Microsoft.Extensions",
|
|
67
|
+
"System.IO",
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
const SECRET_ASSIGNMENT_CANDIDATE_PATTERN =
|
|
71
|
+
/(?:^|[^A-Za-z0-9])(?:(['"])([A-Za-z][A-Za-z0-9_. -]*)\1|([A-Za-z][A-Za-z0-9_.-]*))\s*[=:]\s*[^\s,;]+/gu;
|
|
72
|
+
|
|
73
|
+
const SECRET_LABEL_ASSIGNMENT_PATTERN =
|
|
74
|
+
/\b(?:api[ _-]?key|access[ _-]?token|auth[ _-]?token|credential|credentials|password|passwd|private[ _-]?key|secret|token)\b\s*[=:]\s*[^\s,;]+/iu;
|
|
75
|
+
|
|
76
|
+
const SENSITIVE_ASSIGNMENT_KEY_SUFFIXES = new Set([
|
|
77
|
+
"credential",
|
|
78
|
+
"credentials",
|
|
79
|
+
"password",
|
|
80
|
+
"passwd",
|
|
81
|
+
"secret",
|
|
82
|
+
"token",
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
const COMPACT_SENSITIVE_ASSIGNMENT_KEY_SUFFIXES = [
|
|
86
|
+
"apikey",
|
|
87
|
+
"accesskey",
|
|
88
|
+
"accesskeyid",
|
|
89
|
+
"accesstoken",
|
|
90
|
+
"authtoken",
|
|
91
|
+
"credential",
|
|
92
|
+
"credentials",
|
|
93
|
+
"password",
|
|
94
|
+
"passwd",
|
|
95
|
+
"privatekey",
|
|
96
|
+
"secret",
|
|
97
|
+
"secretkey",
|
|
98
|
+
"token",
|
|
99
|
+
] as const;
|
|
100
|
+
|
|
101
|
+
const SENSITIVE_ASSIGNMENT_KEY_WORD_SUFFIXES = [
|
|
102
|
+
["api", "key"],
|
|
103
|
+
["access", "key"],
|
|
104
|
+
["access", "key", "id"],
|
|
105
|
+
["auth", "key"],
|
|
106
|
+
["private", "key"],
|
|
107
|
+
["secret", "key"],
|
|
108
|
+
] as const;
|
|
109
|
+
|
|
110
|
+
function hasWordSuffix(words: readonly string[], suffix: readonly string[]): boolean {
|
|
111
|
+
if (words.length < suffix.length) return false;
|
|
112
|
+
const offset = words.length - suffix.length;
|
|
113
|
+
return suffix.every((word, index) => words[offset + index] === word);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function containsSensitiveAssignment(value: string): boolean {
|
|
117
|
+
for (const match of value.matchAll(SECRET_ASSIGNMENT_CANDIDATE_PATTERN)) {
|
|
118
|
+
const key = match[2] ?? match[3];
|
|
119
|
+
if (!key) continue;
|
|
120
|
+
const words = key
|
|
121
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
122
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
|
|
123
|
+
.toLowerCase()
|
|
124
|
+
.split(/[^a-z0-9]+/)
|
|
125
|
+
.filter(Boolean);
|
|
126
|
+
const last = words.at(-1);
|
|
127
|
+
if (last && SENSITIVE_ASSIGNMENT_KEY_SUFFIXES.has(last)) return true;
|
|
128
|
+
if (last && COMPACT_SENSITIVE_ASSIGNMENT_KEY_SUFFIXES.some((suffix) => last.endsWith(suffix))) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
if (SENSITIVE_ASSIGNMENT_KEY_WORD_SUFFIXES.some((suffix) => hasWordSuffix(words, suffix))) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function containsUriScheme(value: string): boolean {
|
|
139
|
+
for (const match of value.matchAll(URI_SCHEME_CANDIDATE_PATTERN)) {
|
|
140
|
+
// A Windows drive path is the one scheme-shaped token accepted here. Keep
|
|
141
|
+
// the exception exact: one separator after the drive letter and no later
|
|
142
|
+
// colon, so x://host and a nested scheme remain rejected.
|
|
143
|
+
if (!WINDOWS_DRIVE_PATH_PATTERN.test(match[0])) return true;
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function isDottedTechnologyPath(candidate: string): boolean {
|
|
149
|
+
if (/[?:#]/u.test(candidate)) return false;
|
|
150
|
+
const [authority, ...pathSegments] = candidate.split("/");
|
|
151
|
+
if (!authority || pathSegments.length === 0 || pathSegments.some((segment) => !segment)) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
if (
|
|
155
|
+
!DOTTED_TECHNOLOGY_PATH_AUTHORITIES.has(authority) ||
|
|
156
|
+
pathSegments.some((segment) => !/^[A-Z][A-Za-z0-9_-]*$/u.test(segment))
|
|
157
|
+
) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function containsSchemelessUrl(value: string): boolean {
|
|
164
|
+
if (SCHEMELESS_LOCAL_NETWORK_PATTERNS.some((pattern) => pattern.test(value))) return true;
|
|
165
|
+
|
|
166
|
+
for (const match of value.matchAll(SCHEMELESS_HOST_CANDIDATE_PATTERN)) {
|
|
167
|
+
const candidate = match[0];
|
|
168
|
+
if (candidate.toLowerCase().startsWith("www.")) return true;
|
|
169
|
+
if (!/[/?#]/u.test(candidate)) continue;
|
|
170
|
+
|
|
171
|
+
const authority = candidate.split(/[/?#]/u, 1)[0] ?? "";
|
|
172
|
+
const hostname = authority.replace(/:\d{1,5}$/u, "");
|
|
173
|
+
const suffix = hostname.split(".").at(-1)?.toLowerCase();
|
|
174
|
+
if (suffix && FILE_LIKE_HOST_SUFFIXES.has(suffix)) continue;
|
|
175
|
+
if (isDottedTechnologyPath(candidate)) continue;
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const OPAQUE_IDENTIFIER_PATTERN =
|
|
182
|
+
/\b(?=[A-Za-z0-9_-]{32,}\b)(?=[A-Za-z0-9_-]*[A-Za-z])(?=[A-Za-z0-9_-]*\d)[A-Za-z0-9_-]+\b/u;
|
|
183
|
+
|
|
184
|
+
const DEFAULT_IGNORABLE_CODE_POINTS = /\p{Default_Ignorable_Code_Point}+/gu;
|
|
185
|
+
const ESCAPED_QUOTE_DELIMITERS = /\\+(?=["'])/gu;
|
|
186
|
+
|
|
187
|
+
const TITLE_LABEL_PATTERN =
|
|
188
|
+
/^(?:(?:suggested|generated|concise)\s+)?(?:(?:session|chat|conversation|task)\s+)?title\s*[:\-–—]\s*/iu;
|
|
189
|
+
|
|
190
|
+
const LEADING_BOILERPLATE_PATTERNS = [
|
|
191
|
+
/^(?:hi|hello|hey)(?:\s+there)?\s*[,!:.\-–—]*\s*/iu,
|
|
192
|
+
/^(?:i\s+(?:would\s+like|want|need)\s+you\s+to|i['’]d\s+like\s+you\s+to)\s+/iu,
|
|
193
|
+
/^(?:please\s+)?(?:can|could|would|will)\s+you\s+/iu,
|
|
194
|
+
/^(?:your|the)\s+(?:task|job)\s+is\s+to\s+/iu,
|
|
195
|
+
/^(?:please\s+)?help\s+me\s+(?:to\s+)?/iu,
|
|
196
|
+
/^please(?:\s*[,!:.\-–—]+\s*|\s+|$)/iu,
|
|
197
|
+
] as const;
|
|
198
|
+
|
|
199
|
+
function automaticTitleDetectionValue(value: string): string {
|
|
200
|
+
// Detection uses a compatibility-normalized shadow value so fullwidth
|
|
201
|
+
// punctuation/letters, invisible token splits, and serialized quote
|
|
202
|
+
// delimiters cannot evade the policy.
|
|
203
|
+
// The accepted title itself stays byte-for-byte in the user's language and
|
|
204
|
+
// emoji form; this shadow is never returned or persisted.
|
|
205
|
+
return value
|
|
206
|
+
.normalize("NFKC")
|
|
207
|
+
.replace(DEFAULT_IGNORABLE_CODE_POINTS, "")
|
|
208
|
+
.replace(ESCAPED_QUOTE_DELIMITERS, "");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function hasVisibleAutomaticTitleContent(value: string): boolean {
|
|
212
|
+
return automaticTitleDetectionValue(value).trim().length > 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function containsSensitiveAutomaticTitleValue(value: string): boolean {
|
|
216
|
+
const detectionValue = automaticTitleDetectionValue(value);
|
|
217
|
+
|
|
218
|
+
if (KNOWN_SENSITIVE_VALUE_PATTERNS.some((pattern) => pattern.test(detectionValue))) return true;
|
|
219
|
+
if (containsUriScheme(detectionValue)) return true;
|
|
220
|
+
if (containsSchemelessUrl(detectionValue)) return true;
|
|
221
|
+
if (SECRET_LABEL_ASSIGNMENT_PATTERN.test(detectionValue)) return true;
|
|
222
|
+
if (containsSensitiveAssignment(detectionValue)) return true;
|
|
223
|
+
if (OPAQUE_IDENTIFIER_PATTERN.test(detectionValue)) return true;
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function stripAutomaticTitleBoilerplate(value: string): string {
|
|
228
|
+
let title = value;
|
|
229
|
+
for (let pass = 0; pass < 4; pass += 1) {
|
|
230
|
+
const before = title;
|
|
231
|
+
title = title
|
|
232
|
+
.replace(/^(?:```[^\n]*|[\s#>*_`"'“”‘’\-–—]+)+/u, "")
|
|
233
|
+
.replace(TITLE_LABEL_PATTERN, "");
|
|
234
|
+
for (const pattern of LEADING_BOILERPLATE_PATTERNS) {
|
|
235
|
+
title = title.replace(pattern, "");
|
|
236
|
+
}
|
|
237
|
+
title = title.trim();
|
|
238
|
+
if (title === before) break;
|
|
239
|
+
}
|
|
240
|
+
return title;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function automaticTitleGraphemes(value: string): string[] {
|
|
244
|
+
if (titleSegmenter === undefined) {
|
|
245
|
+
titleSegmenter =
|
|
246
|
+
typeof Intl.Segmenter === "function"
|
|
247
|
+
? new Intl.Segmenter(undefined, { granularity: "grapheme" })
|
|
248
|
+
: null;
|
|
249
|
+
}
|
|
250
|
+
if (titleSegmenter) {
|
|
251
|
+
return Array.from(titleSegmenter.segment(value), (part) => part.segment);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Older embedded runtimes may not ship Intl.Segmenter. Preserve surrogate
|
|
255
|
+
// pairs plus common combining/emoji sequences instead of failing module load
|
|
256
|
+
// or slicing UTF-16 code units.
|
|
257
|
+
const graphemes: string[] = [];
|
|
258
|
+
for (const point of value) {
|
|
259
|
+
const prior = graphemes.at(-1);
|
|
260
|
+
if (
|
|
261
|
+
prior &&
|
|
262
|
+
(/^[\p{Mark}\u{FE0E}\u{FE0F}\p{Emoji_Modifier}]$/u.test(point) ||
|
|
263
|
+
point === "\u200d" ||
|
|
264
|
+
prior.endsWith("\u200d"))
|
|
265
|
+
) {
|
|
266
|
+
graphemes[graphemes.length - 1] = `${prior}${point}`;
|
|
267
|
+
} else {
|
|
268
|
+
graphemes.push(point);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return graphemes;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function boundAutomaticTitle(value: string): string {
|
|
275
|
+
const words = value.split(/\s+/u);
|
|
276
|
+
const wordBounded = words.length > 10 ? words.slice(0, 10).join(" ") : value;
|
|
277
|
+
const graphemes = automaticTitleGraphemes(wordBounded);
|
|
278
|
+
if (graphemes.length <= AUTOMATIC_SESSION_TITLE_MAX_GRAPHEMES) return wordBounded;
|
|
279
|
+
|
|
280
|
+
const prefix = graphemes.slice(0, AUTOMATIC_SESSION_TITLE_MAX_GRAPHEMES).join("");
|
|
281
|
+
const lastWhitespace = prefix.search(/\s+\S*$/u);
|
|
282
|
+
return (lastWhitespace >= 16 ? prefix.slice(0, lastWhitespace) : prefix).trimEnd();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Normalize a model/system-authored title before it reaches durable session
|
|
287
|
+
* metadata. Human renames intentionally do not use this path.
|
|
288
|
+
*
|
|
289
|
+
* Returns null when the candidate is empty, only boilerplate, or appears to
|
|
290
|
+
* contain a credential/opaque prompt value. Callers should retain the current
|
|
291
|
+
* title (normally {@link AUTOMATIC_SESSION_TITLE_FALLBACK}) in that case.
|
|
292
|
+
*/
|
|
293
|
+
export function normalizeAutomaticSessionTitle(value: string): string | null {
|
|
294
|
+
const firstLine = value
|
|
295
|
+
.replace(/[\u0000-\u001f\u007f-\u009f]+/gu, "\n")
|
|
296
|
+
.split(/\n+/u)
|
|
297
|
+
.map((line) => line.trim())
|
|
298
|
+
.find(Boolean);
|
|
299
|
+
if (
|
|
300
|
+
!firstLine ||
|
|
301
|
+
!hasVisibleAutomaticTitleContent(firstLine) ||
|
|
302
|
+
containsSensitiveAutomaticTitleValue(firstLine)
|
|
303
|
+
) {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
let title = stripAutomaticTitleBoilerplate(firstLine)
|
|
308
|
+
.replace(/\s+/gu, " ")
|
|
309
|
+
.replace(/[\s.!?,;:\-–—]+$/u, "")
|
|
310
|
+
.trim();
|
|
311
|
+
if (
|
|
312
|
+
!title ||
|
|
313
|
+
!hasVisibleAutomaticTitleContent(title) ||
|
|
314
|
+
containsSensitiveAutomaticTitleValue(title)
|
|
315
|
+
) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
title = boundAutomaticTitle(title)
|
|
320
|
+
.replace(/[\s.!?,;:\-–—]+$/u, "")
|
|
321
|
+
.trim();
|
|
322
|
+
if (!title || !hasVisibleAutomaticTitleContent(title)) return null;
|
|
323
|
+
return title;
|
|
324
|
+
}
|