@opengeni/sdk 4.4.1 → 4.5.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/README.md +27 -0
- package/dist/adapters/next.js +2 -2
- package/dist/artifacts.js +4 -4
- package/dist/browser.d.ts +2 -0
- package/dist/browser.js +5 -3
- package/dist/chat/index.js +5 -5
- package/dist/{chunk-3BWTS2MJ.js → chunk-ARDVIZIJ.js} +2 -2
- package/dist/{chunk-PVWOBYAZ.js → chunk-BXMNDWAN.js} +256 -78
- package/dist/chunk-BXMNDWAN.js.map +1 -0
- package/dist/{chunk-IVHLADZ7.js → chunk-GRUMGFK6.js} +2 -2
- package/dist/{chunk-2RVPHF37.js → chunk-JBAGMSN2.js} +66 -10
- package/dist/chunk-JBAGMSN2.js.map +1 -0
- package/dist/{chunk-35S72HAE.js → chunk-U4JUWOZV.js} +5 -5
- package/dist/{chunk-BLID3DCD.js → chunk-V4AIZAFF.js} +1 -1
- package/dist/chunk-V4AIZAFF.js.map +1 -0
- package/dist/{chunk-ALXDFL6C.js → chunk-VSOXNL3T.js} +2 -2
- package/dist/{chunk-CT6P2W2C.js → chunk-WI3WUT5H.js} +2 -2
- package/dist/client.d.ts +24 -1
- package/dist/codex-realtime-controller.js +1 -1
- package/dist/core.d.ts +2 -0
- package/dist/core.js +5 -3
- package/dist/document-authority.js +3 -3
- package/dist/editable-artifacts.js +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +35 -33
- package/dist/index.js.map +1 -1
- package/dist/read-retry.d.ts +61 -0
- package/dist/realtime.js +5 -5
- package/dist/session-proxy.d.ts +6 -1
- package/dist/session-proxy.js +2 -2
- package/dist/site-bridge-host.js +3 -3
- package/dist/site-browser-runtime.gen.d.ts +1 -1
- package/dist/site-document.js +1 -1
- package/dist/site-document.js.map +1 -1
- package/dist/site.js +3 -3
- package/dist/types.d.ts +40 -0
- package/dist/workspace-artifacts.d.ts +5 -0
- package/dist/workspace-live-stream.d.ts +7 -0
- package/package.json +3 -3
- package/src/browser.ts +2 -0
- package/src/client.ts +191 -73
- package/src/core.ts +2 -0
- package/src/index.ts +4 -0
- package/src/read-retry.ts +207 -0
- package/src/session-proxy.ts +95 -4
- package/src/site-browser-runtime.gen.ts +1 -1
- package/src/types.ts +38 -0
- package/src/workspace-artifacts.ts +6 -0
- package/src/workspace-live-stream.ts +12 -1
- package/dist/chunk-2RVPHF37.js.map +0 -1
- package/dist/chunk-BLID3DCD.js.map +0 -1
- package/dist/chunk-PVWOBYAZ.js.map +0 -1
- /package/dist/{chunk-3BWTS2MJ.js.map → chunk-ARDVIZIJ.js.map} +0 -0
- /package/dist/{chunk-IVHLADZ7.js.map → chunk-GRUMGFK6.js.map} +0 -0
- /package/dist/{chunk-35S72HAE.js.map → chunk-U4JUWOZV.js.map} +0 -0
- /package/dist/{chunk-ALXDFL6C.js.map → chunk-VSOXNL3T.js.map} +0 -0
- /package/dist/{chunk-CT6P2W2C.js.map → chunk-WI3WUT5H.js.map} +0 -0
package/src/index.ts
CHANGED
|
@@ -346,6 +346,8 @@ export {
|
|
|
346
346
|
isRetryableStreamError,
|
|
347
347
|
} from "./errors";
|
|
348
348
|
export type { OpenGeniSecureContextRequiredReason } from "./errors";
|
|
349
|
+
export { isTransientRequestError } from "./read-retry";
|
|
350
|
+
export type { OpenGeniReadRetryOptions } from "./read-retry";
|
|
349
351
|
export { parseDeprecationNotice } from "./deprecation";
|
|
350
352
|
export type { OpenGeniDeprecationHandler, OpenGeniDeprecationNotice } from "./deprecation";
|
|
351
353
|
export {
|
|
@@ -532,6 +534,7 @@ export type {
|
|
|
532
534
|
PublishWorkspaceArtifactVersionRequest,
|
|
533
535
|
RollbackWorkspaceArtifactRequest,
|
|
534
536
|
SetWorkspaceArtifactStatusRequest,
|
|
537
|
+
SiteOpenedResponse,
|
|
535
538
|
UpdateWorkspaceArtifactVisibilityRequest,
|
|
536
539
|
UpdateWorkspaceArtifactVisibilityResponse,
|
|
537
540
|
WorkspaceArtifactVisibility,
|
|
@@ -1320,6 +1323,7 @@ export type {
|
|
|
1320
1323
|
AgentTopologySession,
|
|
1321
1324
|
SessionListResponse,
|
|
1322
1325
|
SessionListTotals,
|
|
1326
|
+
SiteConversationGroup,
|
|
1323
1327
|
SessionListEntry,
|
|
1324
1328
|
SessionListEntryResponse,
|
|
1325
1329
|
SessionLineageResponse,
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { OpenGeniApiError } from "./errors";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bounded retry for safe reads (`GET` and `HEAD`) that failed transiently:
|
|
5
|
+
* the request never reached a healthy server (a transport error such as a
|
|
6
|
+
* reset connection) or a gateway answered 502, 503 or 504. These are what an
|
|
7
|
+
* API rolling deploy or a short network blip look like from the client.
|
|
8
|
+
* Mutations are never retried here; they keep their outcome-unknown semantics.
|
|
9
|
+
*/
|
|
10
|
+
export type OpenGeniReadRetryOptions = {
|
|
11
|
+
/** Total attempts including the first. Defaults to 4; `1` disables retries. */
|
|
12
|
+
maxAttempts?: number | undefined;
|
|
13
|
+
/** Backoff before the first retry, before jitter. Defaults to 250 ms. Doubles per retry. */
|
|
14
|
+
initialDelayMs?: number | undefined;
|
|
15
|
+
/** Upper bound for one backoff delay. Defaults to 2000 ms. */
|
|
16
|
+
maxDelayMs?: number | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Total time budget measured from the first attempt. A retry, including one
|
|
19
|
+
* whose `Retry-After` asks for a longer wait, is only made when its delay
|
|
20
|
+
* still fits in the budget; otherwise the last error surfaces. Defaults to 5000 ms.
|
|
21
|
+
*/
|
|
22
|
+
maxElapsedMs?: number | undefined;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type ReadRetryPolicy = {
|
|
26
|
+
maxAttempts: number;
|
|
27
|
+
initialDelayMs: number;
|
|
28
|
+
maxDelayMs: number;
|
|
29
|
+
maxElapsedMs: number;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const DEFAULT_READ_RETRY_POLICY: Readonly<ReadRetryPolicy> = Object.freeze({
|
|
33
|
+
maxAttempts: 4,
|
|
34
|
+
initialDelayMs: 250,
|
|
35
|
+
maxDelayMs: 2_000,
|
|
36
|
+
maxElapsedMs: 5_000,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const MAX_READ_RETRY_ATTEMPTS = 10;
|
|
40
|
+
const MAX_READ_RETRY_ELAPSED_MS = 60_000;
|
|
41
|
+
|
|
42
|
+
function boundedNumber(value: unknown, fallback: number, min: number, max: number): number {
|
|
43
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
44
|
+
return Math.min(Math.max(value, min), max);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolve client options to a policy, or `null` when retries are off. Invalid
|
|
49
|
+
* values fall back to the defaults instead of failing client construction.
|
|
50
|
+
*/
|
|
51
|
+
export function resolveReadRetryPolicy(
|
|
52
|
+
options: OpenGeniReadRetryOptions | false | undefined,
|
|
53
|
+
): ReadRetryPolicy | null {
|
|
54
|
+
if (options === false) return null;
|
|
55
|
+
const input = options && typeof options === "object" ? options : {};
|
|
56
|
+
const maxAttempts = Math.floor(
|
|
57
|
+
boundedNumber(
|
|
58
|
+
input.maxAttempts,
|
|
59
|
+
DEFAULT_READ_RETRY_POLICY.maxAttempts,
|
|
60
|
+
1,
|
|
61
|
+
MAX_READ_RETRY_ATTEMPTS,
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
if (maxAttempts <= 1) return null;
|
|
65
|
+
const maxElapsedMs = boundedNumber(
|
|
66
|
+
input.maxElapsedMs,
|
|
67
|
+
DEFAULT_READ_RETRY_POLICY.maxElapsedMs,
|
|
68
|
+
0,
|
|
69
|
+
MAX_READ_RETRY_ELAPSED_MS,
|
|
70
|
+
);
|
|
71
|
+
const maxDelayMs = boundedNumber(
|
|
72
|
+
input.maxDelayMs,
|
|
73
|
+
DEFAULT_READ_RETRY_POLICY.maxDelayMs,
|
|
74
|
+
0,
|
|
75
|
+
MAX_READ_RETRY_ELAPSED_MS,
|
|
76
|
+
);
|
|
77
|
+
const initialDelayMs = boundedNumber(
|
|
78
|
+
input.initialDelayMs,
|
|
79
|
+
DEFAULT_READ_RETRY_POLICY.initialDelayMs,
|
|
80
|
+
0,
|
|
81
|
+
maxDelayMs,
|
|
82
|
+
);
|
|
83
|
+
return { maxAttempts, initialDelayMs, maxDelayMs, maxElapsedMs };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function isSafeReadMethod(method: string): boolean {
|
|
87
|
+
const normalized = method.toUpperCase();
|
|
88
|
+
return normalized === "GET" || normalized === "HEAD";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Gateway statuses a rolling deploy or an overloaded upstream answers with. */
|
|
92
|
+
export function isTransientGatewayStatus(status: number): boolean {
|
|
93
|
+
return status === 502 || status === 503 || status === 504;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Transport failures the client itself observed (`fetch` or the response body
|
|
97
|
+
// rejected with a `TypeError`). Tagged so a caller can tell them apart from a
|
|
98
|
+
// `TypeError` its own code threw, which is a bug, not a transient failure.
|
|
99
|
+
const transportFailures = new WeakSet<object>();
|
|
100
|
+
|
|
101
|
+
/** Record that `error` is a transport failure of an API request. */
|
|
102
|
+
export function markTransportFailure(error: unknown): void {
|
|
103
|
+
if (error instanceof TypeError) transportFailures.add(error);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 502s the client synthesizes for a response it could not accept (a non-JSON
|
|
107
|
+
// or malformed success body). The server did answer; repeating the read gives
|
|
108
|
+
// the same answer, so these are definitive rather than transient.
|
|
109
|
+
const invalidResponses = new WeakSet<object>();
|
|
110
|
+
|
|
111
|
+
/** A 502 for a response the client received but could not accept. */
|
|
112
|
+
export function invalidApiResponseError(
|
|
113
|
+
body: string,
|
|
114
|
+
options?: ConstructorParameters<typeof OpenGeniApiError>[2],
|
|
115
|
+
): OpenGeniApiError {
|
|
116
|
+
const error = new OpenGeniApiError(502, body, options);
|
|
117
|
+
invalidResponses.add(error);
|
|
118
|
+
return error;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Transport failure messages across browsers, Node/undici and Bun, for
|
|
122
|
+
// `TypeError`s that did not pass through this client.
|
|
123
|
+
const NETWORK_FAILURE_MESSAGE =
|
|
124
|
+
/failed to fetch|fetch failed|networkerror|network error|load failed|network request failed|network connection was lost|terminated|err_http2|econnreset|socket hang up/iu;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* A failure a later identical safe read can plausibly succeed after: a
|
|
128
|
+
* transport error (`fetch` or the response body rejected with a `TypeError`)
|
|
129
|
+
* or a retryable 502, 503 or 504. Auth, permission, not-found, validation,
|
|
130
|
+
* rate limits, aborts, other server errors, a success response the client
|
|
131
|
+
* could not accept, and `TypeError`s from application code are definitive and
|
|
132
|
+
* are not transient.
|
|
133
|
+
*/
|
|
134
|
+
export function isTransientRequestError(error: unknown): boolean {
|
|
135
|
+
if (error instanceof OpenGeniApiError) {
|
|
136
|
+
return (
|
|
137
|
+
!invalidResponses.has(error) && isTransientGatewayStatus(error.status) && error.retryable
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (!(error instanceof TypeError)) return false;
|
|
141
|
+
return transportFailures.has(error) || NETWORK_FAILURE_MESSAGE.test(error.message);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** `Retry-After` as milliseconds from `now`, or `null` when absent or invalid. */
|
|
145
|
+
export function parseRetryAfterMs(
|
|
146
|
+
value: string | null | undefined,
|
|
147
|
+
now = Date.now(),
|
|
148
|
+
): number | null {
|
|
149
|
+
if (value === null || value === undefined) return null;
|
|
150
|
+
const trimmed = value.trim();
|
|
151
|
+
if (!trimmed) return null;
|
|
152
|
+
if (/^\d+$/.test(trimmed)) {
|
|
153
|
+
const seconds = Number(trimmed);
|
|
154
|
+
return Number.isSafeInteger(seconds) ? seconds * 1_000 : null;
|
|
155
|
+
}
|
|
156
|
+
// Otherwise only an HTTP-date (it always names the month); never a number.
|
|
157
|
+
if (!/[A-Za-z]/.test(trimmed)) return null;
|
|
158
|
+
const at = Date.parse(trimmed);
|
|
159
|
+
if (!Number.isFinite(at)) return null;
|
|
160
|
+
return Math.max(0, at - now);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* The delay before retry number `retry` (1 for the first retry), or `null`
|
|
165
|
+
* when no retry fits the policy. A server `Retry-After` is honored, plus up to
|
|
166
|
+
* 20% so clients told the same instant do not all return at once; otherwise
|
|
167
|
+
* the exponential backoff is jittered to between half and all of it.
|
|
168
|
+
*/
|
|
169
|
+
export function readRetryDelayMs(
|
|
170
|
+
policy: ReadRetryPolicy,
|
|
171
|
+
retry: number,
|
|
172
|
+
elapsedMs: number,
|
|
173
|
+
retryAfterMs: number | null,
|
|
174
|
+
random: () => number = Math.random,
|
|
175
|
+
): number | null {
|
|
176
|
+
if (retry >= policy.maxAttempts) return null;
|
|
177
|
+
let delay: number;
|
|
178
|
+
if (retryAfterMs !== null) {
|
|
179
|
+
delay = retryAfterMs * (1 + 0.2 * Math.min(Math.max(random(), 0), 1));
|
|
180
|
+
} else {
|
|
181
|
+
const base = Math.min(policy.maxDelayMs, policy.initialDelayMs * 2 ** (retry - 1));
|
|
182
|
+
delay = base / 2 + (base / 2) * Math.min(Math.max(random(), 0), 1);
|
|
183
|
+
}
|
|
184
|
+
if (elapsedMs + delay > policy.maxElapsedMs) return null;
|
|
185
|
+
return Math.round(delay);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Resolve after `ms`, or reject with the signal's reason once it aborts. */
|
|
189
|
+
export function abortableReadRetryDelay(
|
|
190
|
+
ms: number,
|
|
191
|
+
signal: AbortSignal | undefined,
|
|
192
|
+
): Promise<void> {
|
|
193
|
+
if (signal?.aborted) {
|
|
194
|
+
return Promise.reject(signal.reason ?? new DOMException("Request aborted", "AbortError"));
|
|
195
|
+
}
|
|
196
|
+
return new Promise((resolve, reject) => {
|
|
197
|
+
const onAbort = () => {
|
|
198
|
+
clearTimeout(timer);
|
|
199
|
+
reject(signal?.reason ?? new DOMException("Request aborted", "AbortError"));
|
|
200
|
+
};
|
|
201
|
+
const timer = setTimeout(() => {
|
|
202
|
+
signal?.removeEventListener("abort", onAbort);
|
|
203
|
+
resolve();
|
|
204
|
+
}, ms);
|
|
205
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
206
|
+
});
|
|
207
|
+
}
|
package/src/session-proxy.ts
CHANGED
|
@@ -95,7 +95,9 @@ export type SessionProxyContext = {
|
|
|
95
95
|
};
|
|
96
96
|
|
|
97
97
|
/**
|
|
98
|
-
* The only fields a browser may send when creating a session through the proxy
|
|
98
|
+
* The only fields a browser may send when creating a session through the proxy,
|
|
99
|
+
* besides `modelContext`, which the proxy appends after any context the hook
|
|
100
|
+
* and `messageExtras` return, as it does for follow-up messages.
|
|
99
101
|
* After the `createSession` hook returns, the proxy adds the attached files to
|
|
100
102
|
* the created request (deduplicated by file id) and, when `modelSelection` is
|
|
101
103
|
* not `false`, applies the browser's explicit model choices over the hook's,
|
|
@@ -358,6 +360,9 @@ export type SessionProxyHandlerOptions = {
|
|
|
358
360
|
* pinning a chat, moving it to a project, managing projects and a chat's
|
|
359
361
|
* tool approval settings. Every other write is refused.
|
|
360
362
|
* - Catalog logos are served from the Opengeni API.
|
|
363
|
+
* - Creating a chat also takes the app's own create fields: the id of its
|
|
364
|
+
* first message and the new-chat draft revision it sent. It sends
|
|
365
|
+
* `visibility` and `connectionAccounts` too; the server chooses both.
|
|
361
366
|
*/
|
|
362
367
|
sessionApp?: boolean | undefined;
|
|
363
368
|
/**
|
|
@@ -406,6 +411,14 @@ const CREATE_FIELDS: ReadonlySet<string> = new Set([
|
|
|
406
411
|
"idempotencyKey",
|
|
407
412
|
"resources",
|
|
408
413
|
"startMode",
|
|
414
|
+
"modelContext",
|
|
415
|
+
]);
|
|
416
|
+
/** Extra fields the packaged session app sends on create (see `browserCreateFields`). */
|
|
417
|
+
const SESSION_APP_CREATE_FIELDS: ReadonlySet<string> = new Set([
|
|
418
|
+
"visibility",
|
|
419
|
+
"clientEventId",
|
|
420
|
+
"connectionAccounts",
|
|
421
|
+
"expectedNewSessionDraftRevision",
|
|
409
422
|
]);
|
|
410
423
|
const MODEL_FIELDS = ["model", "reasoningEffort", "latencyMode"] as const;
|
|
411
424
|
/** The API's single-recording ceiling (25 MiB) plus multipart framing. */
|
|
@@ -1148,7 +1161,9 @@ export function createSessionProxyHandler(
|
|
|
1148
1161
|
if (method !== "POST" || !options.createSession) {
|
|
1149
1162
|
return errorJson(404, "route_not_allowed", "Not found.");
|
|
1150
1163
|
}
|
|
1151
|
-
const
|
|
1164
|
+
const body = await readJsonBody(request, maxBodyBytes);
|
|
1165
|
+
const input = createInput(body, modelSelection, sessionAppEnabled);
|
|
1166
|
+
const browser = browserCreateFields(body, sessionAppEnabled);
|
|
1152
1167
|
if (input.startMode === "realtime" && !realtimeVoiceEnabled) {
|
|
1153
1168
|
return errorJson(404, "route_not_allowed", "Not found.");
|
|
1154
1169
|
}
|
|
@@ -1157,13 +1172,18 @@ export function createSessionProxyHandler(
|
|
|
1157
1172
|
const created = withBrowserCreateChoices(
|
|
1158
1173
|
toolServer ? withToolServer(hooked, toolServer, await toolToken()) : hooked,
|
|
1159
1174
|
input,
|
|
1175
|
+
browser,
|
|
1160
1176
|
);
|
|
1161
1177
|
const extras = await messageExtras({
|
|
1162
1178
|
delivery: "create",
|
|
1163
1179
|
...(input.startMode ? { startMode: input.startMode } : {}),
|
|
1164
1180
|
});
|
|
1165
1181
|
if (extras instanceof Response) return extras;
|
|
1166
|
-
const modelContext = joinContext(
|
|
1182
|
+
const modelContext = joinContext(
|
|
1183
|
+
extras?.modelContext,
|
|
1184
|
+
created.modelContext,
|
|
1185
|
+
browser.modelContext,
|
|
1186
|
+
);
|
|
1167
1187
|
return json(
|
|
1168
1188
|
await client.createSession(workspaceId, {
|
|
1169
1189
|
...created,
|
|
@@ -2015,9 +2035,14 @@ function pick(
|
|
|
2015
2035
|
function createInput(
|
|
2016
2036
|
body: Record<string, unknown> | undefined,
|
|
2017
2037
|
modelSelection: boolean,
|
|
2038
|
+
sessionApp: boolean,
|
|
2018
2039
|
): SessionProxyCreateInput {
|
|
2019
2040
|
for (const key of Object.keys(body ?? {})) {
|
|
2020
|
-
if (
|
|
2041
|
+
if (
|
|
2042
|
+
!CREATE_FIELDS.has(key) &&
|
|
2043
|
+
!(modelSelection && isModelField(key)) &&
|
|
2044
|
+
!(sessionApp && SESSION_APP_CREATE_FIELDS.has(key))
|
|
2045
|
+
) {
|
|
2021
2046
|
reject(
|
|
2022
2047
|
400,
|
|
2023
2048
|
"create_field_not_allowed",
|
|
@@ -2097,9 +2122,68 @@ function createResources(value: unknown): FileResourceRef[] {
|
|
|
2097
2122
|
}
|
|
2098
2123
|
|
|
2099
2124
|
/** Browser attachments are added; explicit browser model choices win, as for messages. */
|
|
2125
|
+
type BrowserCreateFields = {
|
|
2126
|
+
modelContext?: string | undefined;
|
|
2127
|
+
clientEventId?: string | undefined;
|
|
2128
|
+
expectedNewSessionDraftRevision?: number | undefined;
|
|
2129
|
+
};
|
|
2130
|
+
|
|
2131
|
+
/**
|
|
2132
|
+
* The browser's own context and the session app's create bookkeeping. Message
|
|
2133
|
+
* context is appended after the server's, as on follow-up messages. The session
|
|
2134
|
+
* app also sends the id of its first message event and the revision of the
|
|
2135
|
+
* new-chat draft it sent, so the API can clear that draft. It sends `visibility`
|
|
2136
|
+
* and an empty `connectionAccounts` too: the server chooses both, so those are
|
|
2137
|
+
* dropped, and any account selection is refused.
|
|
2138
|
+
*/
|
|
2139
|
+
function browserCreateFields(
|
|
2140
|
+
body: Record<string, unknown> | undefined,
|
|
2141
|
+
sessionApp: boolean,
|
|
2142
|
+
): BrowserCreateFields {
|
|
2143
|
+
const fields: BrowserCreateFields = {};
|
|
2144
|
+
const modelContext = body?.modelContext;
|
|
2145
|
+
if (modelContext !== undefined && typeof modelContext !== "string") {
|
|
2146
|
+
reject(400, "invalid_model_context", "modelContext must be a string.");
|
|
2147
|
+
}
|
|
2148
|
+
if (modelContext?.trim()) fields.modelContext = modelContext;
|
|
2149
|
+
if (!sessionApp) return fields;
|
|
2150
|
+
const visibility = body?.visibility;
|
|
2151
|
+
if (visibility !== undefined && visibility !== "workspace" && visibility !== "private") {
|
|
2152
|
+
reject(400, "invalid_visibility", "visibility must be workspace or private.");
|
|
2153
|
+
}
|
|
2154
|
+
const accounts = body?.connectionAccounts;
|
|
2155
|
+
if (accounts !== undefined && (!Array.isArray(accounts) || accounts.length > 0)) {
|
|
2156
|
+
reject(
|
|
2157
|
+
403,
|
|
2158
|
+
"connection_account_not_allowed",
|
|
2159
|
+
"The server chooses which connected accounts a chat uses.",
|
|
2160
|
+
);
|
|
2161
|
+
}
|
|
2162
|
+
const clientEventId = body?.clientEventId;
|
|
2163
|
+
if (clientEventId !== undefined) {
|
|
2164
|
+
if (typeof clientEventId !== "string" || !clientEventId) {
|
|
2165
|
+
reject(400, "invalid_client_event_id", "clientEventId must be a non-empty string.");
|
|
2166
|
+
}
|
|
2167
|
+
fields.clientEventId = clientEventId;
|
|
2168
|
+
}
|
|
2169
|
+
const revision = body?.expectedNewSessionDraftRevision;
|
|
2170
|
+
if (revision !== undefined) {
|
|
2171
|
+
if (typeof revision !== "number" || !Number.isSafeInteger(revision) || revision < 0) {
|
|
2172
|
+
reject(
|
|
2173
|
+
400,
|
|
2174
|
+
"invalid_draft_revision",
|
|
2175
|
+
"expectedNewSessionDraftRevision must be a non-negative integer.",
|
|
2176
|
+
);
|
|
2177
|
+
}
|
|
2178
|
+
fields.expectedNewSessionDraftRevision = revision;
|
|
2179
|
+
}
|
|
2180
|
+
return fields;
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2100
2183
|
function withBrowserCreateChoices(
|
|
2101
2184
|
created: CreateSessionRequest,
|
|
2102
2185
|
input: SessionProxyCreateInput,
|
|
2186
|
+
browser: BrowserCreateFields = {},
|
|
2103
2187
|
): CreateSessionRequest {
|
|
2104
2188
|
const existing = created.resources ?? [];
|
|
2105
2189
|
const attached = new Set(
|
|
@@ -2118,6 +2202,13 @@ function withBrowserCreateChoices(
|
|
|
2118
2202
|
...(input.model ? { model: input.model } : {}),
|
|
2119
2203
|
...(input.reasoningEffort ? { reasoningEffort: input.reasoningEffort } : {}),
|
|
2120
2204
|
...(input.latencyMode ? { latencyMode: input.latencyMode } : {}),
|
|
2205
|
+
...(created.clientEventId === undefined && browser.clientEventId
|
|
2206
|
+
? { clientEventId: browser.clientEventId }
|
|
2207
|
+
: {}),
|
|
2208
|
+
...(created.expectedNewSessionDraftRevision === undefined &&
|
|
2209
|
+
browser.expectedNewSessionDraftRevision !== undefined
|
|
2210
|
+
? { expectedNewSessionDraftRevision: browser.expectedNewSessionDraftRevision }
|
|
2211
|
+
: {}),
|
|
2121
2212
|
};
|
|
2122
2213
|
}
|
|
2123
2214
|
|