@opengeni/sdk 3.1.0 → 3.1.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.
- package/README.md +1037 -71
- package/dist/accounts.d.ts +5 -1
- package/dist/accounts.js +13 -1
- package/dist/accounts.js.map +1 -1
- package/dist/adapters/express.d.ts +31 -0
- package/dist/adapters/express.js +87 -0
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/hono.d.ts +16 -0
- package/dist/adapters/hono.js +8 -0
- package/dist/adapters/hono.js.map +1 -0
- package/dist/adapters/next.d.ts +29 -0
- package/dist/adapters/next.js +22 -0
- package/dist/adapters/next.js.map +1 -0
- package/dist/allowance-refusal.d.ts +2 -0
- package/dist/allowance-refusal.js +50 -0
- package/dist/allowance-refusal.js.map +1 -0
- package/dist/app-links.d.ts +42 -0
- package/dist/app-links.js +66 -0
- package/dist/app-links.js.map +1 -0
- package/dist/artifact-catalog.d.ts +40 -0
- package/dist/artifact-client.d.ts +20 -8
- package/dist/artifacts.d.ts +1 -1
- package/dist/artifacts.js +8 -6
- package/dist/automations.js +8 -2
- package/dist/automations.js.map +1 -1
- package/dist/azure-live-transport.d.ts +32 -0
- package/dist/browser.d.ts +7 -2
- package/dist/browser.js +27 -5
- package/dist/chat/fold.d.ts +75 -0
- package/dist/chat/handler.d.ts +41 -0
- package/dist/chat/http.d.ts +67 -0
- package/dist/chat/ids.d.ts +34 -0
- package/dist/chat/index.d.ts +9 -0
- package/dist/chat/index.js +1703 -0
- package/dist/chat/index.js.map +1 -0
- package/dist/chat/openai.d.ts +30 -0
- package/dist/chat/opengeni.d.ts +129 -0
- package/dist/chat/types.d.ts +207 -0
- package/dist/chat/vercel.d.ts +49 -0
- package/dist/chats.d.ts +8 -0
- package/dist/chunk-4AHR4KGA.js +301 -0
- package/dist/chunk-4AHR4KGA.js.map +1 -0
- package/dist/chunk-6HZGAIUE.js +1665 -0
- package/dist/chunk-6HZGAIUE.js.map +1 -0
- package/dist/chunk-7EWT3PV6.js +368 -0
- package/dist/chunk-7EWT3PV6.js.map +1 -0
- package/dist/{chunk-VYCTL62C.js → chunk-GZQSWAKD.js} +1 -1
- package/dist/chunk-GZQSWAKD.js.map +1 -0
- package/dist/chunk-JXXCTW5W.js +1392 -0
- package/dist/chunk-JXXCTW5W.js.map +1 -0
- package/dist/{chunk-YTAWBDO2.js → chunk-KADZ5PGG.js} +28 -2
- package/dist/chunk-KADZ5PGG.js.map +1 -0
- package/dist/{chunk-YKZME56C.js → chunk-KWADQ227.js} +3 -3
- package/dist/{chunk-YKZME56C.js.map → chunk-KWADQ227.js.map} +1 -1
- package/dist/{chunk-7ZXBPJZP.js → chunk-NEM5N6C6.js} +3902 -1457
- package/dist/chunk-NEM5N6C6.js.map +1 -0
- package/dist/{chunk-GW3EJ2GP.js → chunk-O7KANXC3.js} +86 -13
- package/dist/chunk-O7KANXC3.js.map +1 -0
- package/dist/{chunk-VUQZAB3O.js → chunk-OAKHWFSV.js} +28 -32
- package/dist/chunk-OAKHWFSV.js.map +1 -0
- package/dist/chunk-P7UWKORG.js +561 -0
- package/dist/chunk-P7UWKORG.js.map +1 -0
- package/dist/chunk-QGEIL4TR.js +195 -0
- package/dist/chunk-QGEIL4TR.js.map +1 -0
- package/dist/chunk-RJHCBZF7.js +270 -0
- package/dist/chunk-RJHCBZF7.js.map +1 -0
- package/dist/chunk-S3D75WSQ.js +340 -0
- package/dist/chunk-S3D75WSQ.js.map +1 -0
- package/dist/chunk-UM4ZXXJR.js +9 -0
- package/dist/chunk-UM4ZXXJR.js.map +1 -0
- package/dist/chunk-VPVMLM7Q.js +15 -0
- package/dist/chunk-VPVMLM7Q.js.map +1 -0
- package/dist/{chunk-C2H7HBNP.js → chunk-WVCJRSGJ.js} +119 -14
- package/dist/chunk-WVCJRSGJ.js.map +1 -0
- package/dist/chunk-XA7HVIHI.js +126 -0
- package/dist/chunk-XA7HVIHI.js.map +1 -0
- package/dist/{chunk-TX3EIENU.js → chunk-YDVPI7WT.js} +73 -2
- package/dist/chunk-YDVPI7WT.js.map +1 -0
- package/dist/chunk-YKIHNLWD.js +153 -0
- package/dist/chunk-YKIHNLWD.js.map +1 -0
- package/dist/chunk-ZDUBPV33.js +277 -0
- package/dist/chunk-ZDUBPV33.js.map +1 -0
- package/dist/client.d.ts +1065 -124
- package/dist/codex-realtime-controller.d.ts +13 -0
- package/dist/codex-realtime-controller.js +4 -2
- package/dist/codex-realtime-v3-wire.d.ts +3 -2
- package/dist/codex-realtime.d.ts +5 -1
- package/dist/company-brain.d.ts +1 -11
- package/dist/company-profile.d.ts +13 -0
- package/dist/core.d.ts +7 -2
- package/dist/core.js +25 -6
- package/dist/deprecation.d.ts +47 -0
- package/dist/document-authority-client.d.ts +24 -2
- package/dist/document-authority.js +7 -5
- package/dist/editable-artifacts/index.d.ts +1 -1
- package/dist/editable-artifacts/types.d.ts +12 -2
- package/dist/editable-artifacts.js +19 -3
- package/dist/editable-artifacts.js.map +1 -1
- package/dist/embedding-client.d.ts +129 -0
- package/dist/errors.d.ts +76 -1
- package/dist/feedback.d.ts +23 -0
- package/dist/gateway-realtime-transport.js +1 -1
- package/dist/github-repositories.d.ts +10 -0
- package/dist/github-repositories.js +36 -0
- package/dist/github-repositories.js.map +1 -0
- package/dist/index.d.ts +64 -11
- package/dist/index.js +566 -141
- package/dist/index.js.map +1 -1
- package/dist/insights-usage.d.ts +33 -0
- package/dist/insights-usage.js +7 -0
- package/dist/insights-usage.js.map +1 -0
- package/dist/interaction.d.ts +103 -71
- package/dist/interaction.js +4 -2
- package/dist/knowledge.d.ts +7 -0
- package/dist/knowledge.js +20 -0
- package/dist/knowledge.js.map +1 -0
- package/dist/known-tools.d.ts +4 -0
- package/dist/message-links.d.ts +68 -0
- package/dist/model-context.d.ts +68 -0
- package/dist/model-display.d.ts +2 -0
- package/dist/model-display.js +17 -0
- package/dist/model-display.js.map +1 -0
- package/dist/model-picker-order.d.ts +1 -0
- package/dist/model-picker-order.js +10 -0
- package/dist/model-picker-order.js.map +1 -0
- package/dist/organization-integration-policy.d.ts +26 -0
- package/dist/organization-integration-policy.js +26 -0
- package/dist/organization-integration-policy.js.map +1 -0
- package/dist/pr-review-client.d.ts +1 -1
- package/dist/pr-review.d.ts +1 -1
- package/dist/pr-review.js.map +1 -1
- package/dist/preference-registry.d.ts +1 -1
- package/dist/prepared-mcp-setup.d.ts +5 -0
- package/dist/proxy.d.ts +13 -11
- package/dist/realtime.d.ts +4 -2
- package/dist/realtime.js +253 -7
- package/dist/realtime.js.map +1 -1
- package/dist/session-history-import.d.ts +22 -0
- package/dist/session-history-import.js +36 -0
- package/dist/session-history-import.js.map +1 -0
- package/dist/session-list-entries.d.ts +1 -0
- package/dist/session-list-entries.js +7 -0
- package/dist/session-list-entries.js.map +1 -0
- package/dist/session-message-search.d.ts +53 -0
- package/dist/session-proxy-client.d.ts +28 -0
- package/dist/session-proxy.d.ts +334 -0
- package/dist/session-proxy.js +24 -0
- package/dist/session-proxy.js.map +1 -0
- package/dist/session-titles.d.ts +2 -0
- package/dist/session-usage-limits.d.ts +10 -0
- package/dist/session-usage-limits.js +43 -0
- package/dist/session-usage-limits.js.map +1 -0
- package/dist/shared-catalog-loader.d.ts +34 -0
- package/dist/site-bridge-host.d.ts +112 -0
- package/dist/site-bridge-host.js +285 -0
- package/dist/site-bridge-host.js.map +1 -0
- package/dist/site-browser-entry.d.ts +1 -0
- package/dist/site-browser-runtime.gen.d.ts +1 -0
- package/dist/site-data.d.ts +178 -0
- package/dist/site-document.d.ts +5 -0
- package/dist/site-document.js +20 -0
- package/dist/site-document.js.map +1 -0
- package/dist/site-document.test.d.ts +1 -0
- package/dist/site-http.d.ts +17 -0
- package/dist/site-sql.d.ts +31 -0
- package/dist/site-tool-bridge.d.ts +57 -0
- package/dist/site.d.ts +182 -0
- package/dist/site.js +62 -0
- package/dist/site.js.map +1 -0
- package/dist/skills.d.ts +75 -0
- package/dist/sse.d.ts +1 -1
- package/dist/stream.d.ts +2 -0
- package/dist/tenant-workspaces.d.ts +47 -0
- package/dist/tenant-workspaces.js +8 -0
- package/dist/tenant-workspaces.js.map +1 -0
- package/dist/tool-auth.d.ts +87 -0
- package/dist/tool-auth.js +23 -0
- package/dist/tool-auth.js.map +1 -0
- package/dist/tool-display-metadata.d.ts +3 -0
- package/dist/tools-37DCTCNZ.js +12 -0
- package/dist/tools-37DCTCNZ.js.map +1 -0
- package/dist/tools.d.ts +81 -0
- package/dist/transcription.d.ts +1 -1
- package/dist/types.d.ts +2848 -573
- package/dist/usage-allowances.d.ts +36 -0
- package/dist/usage-allowances.js +80 -0
- package/dist/usage-allowances.js.map +1 -0
- package/dist/vault-types.d.ts +236 -0
- package/dist/vaults-api.d.ts +57 -0
- package/dist/workspace-artifacts.d.ts +47 -5
- package/dist/workspace-instruction-policies.d.ts +2 -2
- package/dist/workspace-integrations.d.ts +74 -0
- package/dist/workspace-integrations.js +65 -0
- package/dist/workspace-integrations.js.map +1 -0
- package/dist/workspace-state.d.ts +8 -71
- package/package.json +99 -5
- package/src/accounts.ts +18 -0
- package/src/adapters/express.ts +136 -0
- package/src/adapters/hono.ts +18 -0
- package/src/adapters/next.ts +42 -0
- package/src/allowance-refusal.ts +5 -0
- package/src/app-links.ts +117 -0
- package/src/artifact-catalog.ts +51 -0
- package/src/artifact-client.ts +40 -49
- package/src/artifacts.ts +1 -0
- package/src/automations-client.ts +8 -2
- package/src/azure-live-transport.ts +286 -0
- package/src/browser.ts +37 -0
- package/src/chat/fold.ts +431 -0
- package/src/chat/handler.ts +240 -0
- package/src/chat/http.ts +257 -0
- package/src/chat/ids.ts +85 -0
- package/src/chat/index.ts +88 -0
- package/src/chat/openai.ts +439 -0
- package/src/chat/opengeni.ts +737 -0
- package/src/chat/types.ts +219 -0
- package/src/chat/vercel.ts +204 -0
- package/src/chats.ts +16 -0
- package/src/client.ts +5822 -1847
- package/src/codex-realtime-controller.ts +163 -8
- package/src/codex-realtime-v3-wire.ts +4 -3
- package/src/codex-realtime-v3.ts +12 -3
- package/src/codex-realtime.ts +11 -3
- package/src/company-brain.ts +0 -40
- package/src/company-profile.ts +13 -0
- package/src/core.ts +20 -0
- package/src/deprecation.ts +149 -0
- package/src/document-authority-client.ts +104 -1
- package/src/editable-artifacts/controller.ts +36 -2
- package/src/editable-artifacts/index.ts +1 -0
- package/src/editable-artifacts/types.ts +29 -16
- package/src/embedding-client.ts +519 -0
- package/src/errors.ts +303 -4
- package/src/feedback.ts +20 -0
- package/src/gateway-realtime-transport.ts +3 -3
- package/src/github-repositories.ts +60 -0
- package/src/index.ts +611 -64
- package/src/insights-usage.ts +61 -0
- package/src/interaction.ts +274 -93
- package/src/knowledge.ts +72 -0
- package/src/known-tools.ts +279 -0
- package/src/message-links.ts +178 -0
- package/src/model-context.ts +113 -0
- package/src/model-display.ts +11 -0
- package/src/model-picker-order.ts +6 -0
- package/src/organization-integration-policy.ts +54 -0
- package/src/pr-review-client.ts +1 -1
- package/src/pr-review.ts +1 -1
- package/src/preference-registry.ts +2 -0
- package/src/prepared-mcp-setup.ts +123 -0
- package/src/proxy.ts +23 -12
- package/src/realtime.ts +16 -6
- package/src/session-history-import.ts +84 -0
- package/src/session-list-entries.ts +1 -0
- package/src/session-message-search.ts +47 -0
- package/src/session-proxy-client.ts +68 -0
- package/src/session-proxy.ts +2098 -0
- package/src/session-titles.ts +10 -0
- package/src/session-usage-limits.ts +71 -0
- package/src/shared-catalog-loader.ts +169 -0
- package/src/site-bridge-host.ts +422 -0
- package/src/site-browser-entry.ts +6 -0
- package/src/site-browser-runtime.gen.ts +2 -0
- package/src/site-data.ts +372 -0
- package/src/site-document.test.ts +64 -0
- package/src/site-document.ts +18 -0
- package/src/site-http.ts +171 -0
- package/src/site-sql.ts +153 -0
- package/src/site-tool-bridge.ts +401 -0
- package/src/site.ts +867 -0
- package/src/skills.ts +69 -0
- package/src/sse.ts +9 -3
- package/src/stream.ts +33 -10
- package/src/tenant-workspaces.ts +148 -0
- package/src/tool-auth.ts +301 -0
- package/src/tool-display-metadata.ts +27 -0
- package/src/tools.ts +452 -0
- package/src/transcription.ts +3 -0
- package/src/types.ts +3216 -581
- package/src/usage-allowances.ts +189 -0
- package/src/vault-types.ts +293 -0
- package/src/vaults-api.ts +288 -0
- package/src/workspace-artifacts.ts +55 -6
- package/src/workspace-instruction-policies.ts +2 -1
- package/src/workspace-integrations.ts +402 -0
- package/src/workspace-state.ts +8 -82
- package/dist/chunk-7ZXBPJZP.js.map +0 -1
- package/dist/chunk-C2H7HBNP.js.map +0 -1
- package/dist/chunk-C4DJPZRU.js +0 -119
- package/dist/chunk-C4DJPZRU.js.map +0 -1
- package/dist/chunk-GW3EJ2GP.js.map +0 -1
- package/dist/chunk-TX3EIENU.js.map +0 -1
- package/dist/chunk-VUQZAB3O.js.map +0 -1
- package/dist/chunk-VYCTL62C.js.map +0 -1
- package/dist/chunk-YTAWBDO2.js.map +0 -1
|
@@ -0,0 +1,1665 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chatDefaults
|
|
3
|
+
} from "./chunk-UM4ZXXJR.js";
|
|
4
|
+
import {
|
|
5
|
+
mintToolToken,
|
|
6
|
+
resolveToolTokenSecret
|
|
7
|
+
} from "./chunk-QGEIL4TR.js";
|
|
8
|
+
import {
|
|
9
|
+
sessionEventStreamCoveredThrough
|
|
10
|
+
} from "./chunk-4AHR4KGA.js";
|
|
11
|
+
import {
|
|
12
|
+
OPENGENI_API_CONTRACT_HEADER,
|
|
13
|
+
OPENGENI_API_CONTRACT_REVISION
|
|
14
|
+
} from "./chunk-KADZ5PGG.js";
|
|
15
|
+
import {
|
|
16
|
+
OpenGeniApiError,
|
|
17
|
+
OpenGeniSetupError
|
|
18
|
+
} from "./chunk-7EWT3PV6.js";
|
|
19
|
+
|
|
20
|
+
// src/message-links.ts
|
|
21
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
|
|
22
|
+
var EDITABLE_ID = /^[0-9a-f]{32}$/iu;
|
|
23
|
+
var WORKSPACE_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/u;
|
|
24
|
+
var SANDBOX_SCHEME = "sandbox:";
|
|
25
|
+
var LEGACY_WORKSPACE_PREFIX = "/workspace/";
|
|
26
|
+
var TRAILING_LINE = /:([0-9]+)$/u;
|
|
27
|
+
var TRAILING_INVALID_LINE = /:-(?:[0-9]+)$|:[0-9]+-[0-9]+$/u;
|
|
28
|
+
var CONTROL_CHARACTER = /[\u0000-\u001f\u007f]/u;
|
|
29
|
+
function openGeniLinkScheme(href) {
|
|
30
|
+
const scheme = /^([a-z]+):/iu.exec(href ?? "")?.[1]?.toLowerCase();
|
|
31
|
+
return scheme === "artifact" || scheme === "sandbox" ? scheme : null;
|
|
32
|
+
}
|
|
33
|
+
function isReservedOpenGeniLink(href) {
|
|
34
|
+
if (!href) return false;
|
|
35
|
+
if (openGeniLinkScheme(href) || href.startsWith(LEGACY_WORKSPACE_PREFIX)) return true;
|
|
36
|
+
try {
|
|
37
|
+
return /^\/workspaces\/[^/]+\/artifacts(?:\/|[?#]|$)/u.test(decodeURIComponent(href));
|
|
38
|
+
} catch {
|
|
39
|
+
return /^\/workspaces\/[^/]+\/artifacts/u.test(href);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function parseSandboxLink(href) {
|
|
43
|
+
if (!href) return null;
|
|
44
|
+
const encodedLocation = openGeniLinkScheme(href) === "sandbox" ? href.slice(SANDBOX_SCHEME.length) : href.startsWith(LEGACY_WORKSPACE_PREFIX) ? href : null;
|
|
45
|
+
if (!encodedLocation || encodedLocation.includes("?") || encodedLocation.includes("#")) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
if (TRAILING_INVALID_LINE.test(encodedLocation)) return null;
|
|
49
|
+
const lineMatch = TRAILING_LINE.exec(encodedLocation);
|
|
50
|
+
const encodedPath = lineMatch ? encodedLocation.slice(0, -lineMatch[0].length) : encodedLocation;
|
|
51
|
+
const line = lineMatch?.[1] ? Number(lineMatch[1]) : null;
|
|
52
|
+
if (line !== null && (!Number.isSafeInteger(line) || line < 1)) return null;
|
|
53
|
+
if (!encodedPath) return null;
|
|
54
|
+
try {
|
|
55
|
+
const path = decodeURIComponent(encodedPath);
|
|
56
|
+
return path && !CONTROL_CHARACTER.test(path) ? { path, line } : null;
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function parseRetainedFileReference(href) {
|
|
62
|
+
if (openGeniLinkScheme(href) !== "artifact") return null;
|
|
63
|
+
if (!href) return null;
|
|
64
|
+
const id = href.slice("artifact:".length);
|
|
65
|
+
return UUID.test(id) ? id.toLowerCase() : null;
|
|
66
|
+
}
|
|
67
|
+
function parseOpenGeniLink(href) {
|
|
68
|
+
if (!href) return null;
|
|
69
|
+
const fileId = parseRetainedFileReference(href);
|
|
70
|
+
if (fileId) return { kind: "file", fileId, workspaceId: null };
|
|
71
|
+
if (openGeniLinkScheme(href) === "sandbox" || href.startsWith(LEGACY_WORKSPACE_PREFIX)) {
|
|
72
|
+
const location = parseSandboxLink(href);
|
|
73
|
+
return location ? { kind: "sandbox-file", ...location } : null;
|
|
74
|
+
}
|
|
75
|
+
if (!href.startsWith("/workspaces/") || href.includes("#")) return null;
|
|
76
|
+
const separator = href.indexOf("?");
|
|
77
|
+
const pathname = separator < 0 ? href : href.slice(0, separator);
|
|
78
|
+
const search = separator < 0 ? void 0 : href.slice(separator + 1);
|
|
79
|
+
let fromSession;
|
|
80
|
+
if (search !== void 0) {
|
|
81
|
+
const params = new URLSearchParams(search);
|
|
82
|
+
const keys = [...params.keys()];
|
|
83
|
+
if (keys.some((key) => key !== "fromSession") || keys.length > 1) return null;
|
|
84
|
+
if (!UUID.test(params.get("fromSession") ?? "")) return null;
|
|
85
|
+
fromSession = params.get("fromSession").toLowerCase();
|
|
86
|
+
}
|
|
87
|
+
const parts = pathname.split("/");
|
|
88
|
+
const workspaceId = parts[2] ?? "";
|
|
89
|
+
if (parts[1] !== "workspaces" || parts[3] !== "artifacts" || !WORKSPACE_SEGMENT.test(workspaceId))
|
|
90
|
+
return null;
|
|
91
|
+
if (parts.length === 6 && parts[4] === "editable" && EDITABLE_ID.test(parts[5] ?? "")) {
|
|
92
|
+
return {
|
|
93
|
+
kind: "editable-artifact",
|
|
94
|
+
artifactId: parts[5].toLowerCase(),
|
|
95
|
+
workspaceId,
|
|
96
|
+
...fromSession ? { fromSession } : {}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (parts.length === 6 && parts[4] === "files" && UUID.test(parts[5] ?? "")) {
|
|
100
|
+
return {
|
|
101
|
+
kind: "file",
|
|
102
|
+
fileId: parts[5].toLowerCase(),
|
|
103
|
+
workspaceId,
|
|
104
|
+
...fromSession ? { fromSession } : {}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (parts.length === 5 && UUID.test(parts[4] ?? "")) {
|
|
108
|
+
return {
|
|
109
|
+
kind: "site",
|
|
110
|
+
artifactId: parts[4].toLowerCase(),
|
|
111
|
+
workspaceId,
|
|
112
|
+
...fromSession ? { fromSession } : {}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
function openGeniConsolePath(target, workspaceId) {
|
|
118
|
+
const workspace = encodeURIComponent(
|
|
119
|
+
target.kind === "sandbox-file" ? workspaceId : target.workspaceId ?? workspaceId
|
|
120
|
+
);
|
|
121
|
+
const search = target.kind !== "sandbox-file" && target.fromSession && UUID.test(target.fromSession) ? `?fromSession=${encodeURIComponent(target.fromSession)}` : "";
|
|
122
|
+
switch (target.kind) {
|
|
123
|
+
case "file":
|
|
124
|
+
return `/workspaces/${workspace}/artifacts/files/${target.fileId}${search}`;
|
|
125
|
+
case "editable-artifact":
|
|
126
|
+
return `/workspaces/${workspace}/artifacts/editable/${target.artifactId}${search}`;
|
|
127
|
+
case "site":
|
|
128
|
+
return `/workspaces/${workspace}/artifacts/${target.artifactId}${search}`;
|
|
129
|
+
case "sandbox-file":
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
var SESSION_SCOPE_HEADER = "x-opengeni-session-id";
|
|
134
|
+
|
|
135
|
+
// src/proxy.ts
|
|
136
|
+
function formatSseEvent(event) {
|
|
137
|
+
const streamedCoverage = sessionEventStreamCoveredThrough(event);
|
|
138
|
+
const projectedCoverage = event.coveredThrough;
|
|
139
|
+
const coveredThrough = streamedCoverage ?? (typeof projectedCoverage === "number" && Number.isSafeInteger(projectedCoverage) && projectedCoverage >= event.sequence ? projectedCoverage : event.sequence);
|
|
140
|
+
return `id: ${coveredThrough}
|
|
141
|
+
event: ${event.type}
|
|
142
|
+
data: ${JSON.stringify(event)}
|
|
143
|
+
|
|
144
|
+
`;
|
|
145
|
+
}
|
|
146
|
+
function sessionEventsToSseStream(events, options = {}) {
|
|
147
|
+
const encoder = new TextEncoder();
|
|
148
|
+
const iterator = events[Symbol.asyncIterator]();
|
|
149
|
+
let heartbeat;
|
|
150
|
+
let cancelled = false;
|
|
151
|
+
const stopHeartbeat = () => {
|
|
152
|
+
if (heartbeat !== void 0) {
|
|
153
|
+
clearInterval(heartbeat);
|
|
154
|
+
heartbeat = void 0;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
return new ReadableStream({
|
|
158
|
+
start: (controller) => {
|
|
159
|
+
if (options.heartbeatMs !== void 0) {
|
|
160
|
+
heartbeat = setInterval(() => {
|
|
161
|
+
try {
|
|
162
|
+
controller.enqueue(encoder.encode(": ping\n\n"));
|
|
163
|
+
} catch {
|
|
164
|
+
stopHeartbeat();
|
|
165
|
+
}
|
|
166
|
+
}, options.heartbeatMs);
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
pull: async (controller) => {
|
|
170
|
+
let result;
|
|
171
|
+
try {
|
|
172
|
+
result = await iterator.next();
|
|
173
|
+
} catch (error) {
|
|
174
|
+
stopHeartbeat();
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
if (cancelled) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (result.done) {
|
|
181
|
+
stopHeartbeat();
|
|
182
|
+
controller.close();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
controller.enqueue(encoder.encode(formatSseEvent(result.value)));
|
|
186
|
+
},
|
|
187
|
+
cancel: () => {
|
|
188
|
+
cancelled = true;
|
|
189
|
+
stopHeartbeat();
|
|
190
|
+
options.onCancel?.();
|
|
191
|
+
void Promise.resolve(iterator.return?.(void 0)).then(
|
|
192
|
+
() => void 0,
|
|
193
|
+
() => void 0
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function sessionEventsToSseResponse(events, options = {}) {
|
|
199
|
+
return new Response(sessionEventsToSseStream(events, options), {
|
|
200
|
+
headers: {
|
|
201
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
202
|
+
"Cache-Control": "no-cache, no-transform",
|
|
203
|
+
Connection: "keep-alive"
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
function resumeSequenceFromRequest(request) {
|
|
208
|
+
const url = new URL(request.url);
|
|
209
|
+
const raw = url.searchParams.get("after") ?? request.headers.get("Last-Event-ID");
|
|
210
|
+
const parsed = raw === null ? 0 : Number(raw);
|
|
211
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : 0;
|
|
212
|
+
}
|
|
213
|
+
function proxySessionEventStream(client, workspaceId, sessionId, options = {}) {
|
|
214
|
+
const { after, heartbeatMs, signal, ...streamOptions } = options;
|
|
215
|
+
const upstream = new AbortController();
|
|
216
|
+
if (signal?.aborted) {
|
|
217
|
+
upstream.abort();
|
|
218
|
+
} else {
|
|
219
|
+
signal?.addEventListener("abort", () => upstream.abort(), { once: true });
|
|
220
|
+
}
|
|
221
|
+
const resolvedAfter = after instanceof Request ? resumeSequenceFromRequest(after) : after ?? 0;
|
|
222
|
+
const events = client.streamEvents(workspaceId, sessionId, {
|
|
223
|
+
...streamOptions,
|
|
224
|
+
after: resolvedAfter,
|
|
225
|
+
signal: upstream.signal
|
|
226
|
+
});
|
|
227
|
+
return sessionEventsToSseResponse(events, {
|
|
228
|
+
...heartbeatMs !== void 0 ? { heartbeatMs } : {},
|
|
229
|
+
onCancel: () => upstream.abort()
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/session-proxy-client.ts
|
|
234
|
+
var SESSION_PROXY_SITE_HTML_MAX_BYTES = 25 * 1024 * 1024;
|
|
235
|
+
var SessionProxySiteHtmlTooLargeError = class extends Error {
|
|
236
|
+
code = "site_html_too_large";
|
|
237
|
+
limitBytes = SESSION_PROXY_SITE_HTML_MAX_BYTES;
|
|
238
|
+
constructor() {
|
|
239
|
+
super(`Site HTML exceeds the ${SESSION_PROXY_SITE_HTML_MAX_BYTES}-byte proxy limit.`);
|
|
240
|
+
this.name = "SessionProxySiteHtmlTooLargeError";
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
function getSessionProxyWorkspaceGrant(client, workspaceId, options = {}) {
|
|
244
|
+
return client.requestJson(
|
|
245
|
+
"GET",
|
|
246
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/access/grant`,
|
|
247
|
+
void 0,
|
|
248
|
+
{},
|
|
249
|
+
options
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
function getSessionProxyArtifactAssociation(client, workspaceId, sessionId, kind, artifactId, options = {}) {
|
|
253
|
+
return client.requestJson(
|
|
254
|
+
"GET",
|
|
255
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/sessions/${encodeURIComponent(sessionId)}/artifact-associations/${encodeURIComponent(artifactId)}`,
|
|
256
|
+
void 0,
|
|
257
|
+
{ kind },
|
|
258
|
+
options
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
function downloadSessionProxySiteHtml(client, workspaceId, artifactId, options) {
|
|
262
|
+
return client.requestJsonResponse(
|
|
263
|
+
`/v1/workspaces/${encodeURIComponent(workspaceId)}/published-artifacts/${encodeURIComponent(artifactId)}/html`,
|
|
264
|
+
{ versionId: options.versionId },
|
|
265
|
+
options
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/session-proxy.ts
|
|
270
|
+
var SESSION_PROXY_DEFAULT_HEARTBEAT_MS = 5e3;
|
|
271
|
+
var DEFAULT_MAX_BODY_BYTES = 1024 * 1024;
|
|
272
|
+
var SEGMENT = /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/;
|
|
273
|
+
var QUEUE_OPERATIONS = /* @__PURE__ */ new Set(["move", "edit", "steer", "delete"]);
|
|
274
|
+
var CREATE_FIELDS = /* @__PURE__ */ new Set([
|
|
275
|
+
"initialMessage",
|
|
276
|
+
"idempotencyKey",
|
|
277
|
+
"resources",
|
|
278
|
+
"startMode"
|
|
279
|
+
]);
|
|
280
|
+
var MODEL_FIELDS = ["model", "reasoningEffort", "latencyMode"];
|
|
281
|
+
var MAX_TRANSCRIPTION_BODY_BYTES = 25 * 1024 * 1024 + 64 * 1024;
|
|
282
|
+
var ProxyRejection = class extends Error {
|
|
283
|
+
constructor(status, code, message) {
|
|
284
|
+
super(message);
|
|
285
|
+
this.status = status;
|
|
286
|
+
this.code = code;
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
function reject(status, code, message) {
|
|
290
|
+
throw new ProxyRejection(status, code, message);
|
|
291
|
+
}
|
|
292
|
+
function json(body, status = 200, headers = {}) {
|
|
293
|
+
return new Response(JSON.stringify(body), {
|
|
294
|
+
status,
|
|
295
|
+
headers: {
|
|
296
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
297
|
+
"Cache-Control": "no-store",
|
|
298
|
+
...headers
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
function errorJson(status, code, message) {
|
|
303
|
+
return json({ error: { code, message } }, status);
|
|
304
|
+
}
|
|
305
|
+
function isFacade(target) {
|
|
306
|
+
return !("asUser" in target) && "client" in target;
|
|
307
|
+
}
|
|
308
|
+
function createSessionProxyHandler(target, options) {
|
|
309
|
+
const service = isFacade(target) ? target.client : target;
|
|
310
|
+
const defaultSource = isFacade(target) ? target.source : "default";
|
|
311
|
+
const rejectedKeyNotice = { sent: false };
|
|
312
|
+
const maxBodyBytes = options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
|
|
313
|
+
const filesEnabled = options.files ?? true;
|
|
314
|
+
const voiceInputEnabled = options.voiceInput ?? true;
|
|
315
|
+
const realtimeVoiceEnabled = options.realtimeVoice ?? true;
|
|
316
|
+
const sandboxFilesEnabled = options.sandboxFiles === true;
|
|
317
|
+
const artifactsEnabled = options.artifacts !== void 0 && options.artifacts !== false;
|
|
318
|
+
const sitesEnabled = options.sites === true;
|
|
319
|
+
const editableLiveUrl = artifactsEnabled ? liveSocketUrl(
|
|
320
|
+
(typeof options.artifacts === "object" ? options.artifacts.editableLiveUrl : void 0) ?? service.apiUrl(EDITABLE_ARTIFACT_LIVE_PATH)
|
|
321
|
+
) : null;
|
|
322
|
+
const modelSelection = options.modelSelection ?? true;
|
|
323
|
+
const heartbeatMs = options.heartbeatMs ?? SESSION_PROXY_DEFAULT_HEARTBEAT_MS;
|
|
324
|
+
const sessionList = options.sessionList ?? "mine";
|
|
325
|
+
const archiveEnabled = options.archive ?? true;
|
|
326
|
+
const chats = options.chats ?? "private";
|
|
327
|
+
const defaults = chatDefaults(chats);
|
|
328
|
+
const toolServer = options.toolServer ? normalizeToolServer(options.toolServer) : void 0;
|
|
329
|
+
const toolSessions = /* @__PURE__ */ new Map();
|
|
330
|
+
const subjects = /* @__PURE__ */ new Map();
|
|
331
|
+
const subjectOf = (client, key) => {
|
|
332
|
+
let subject = subjects.get(key);
|
|
333
|
+
if (!subject) {
|
|
334
|
+
subject = client.getAccessContext().then((access) => access.subjectId);
|
|
335
|
+
subject.catch(() => subjects.delete(key));
|
|
336
|
+
if (subjects.size >= 1e3) subjects.delete(subjects.keys().next().value);
|
|
337
|
+
subjects.set(key, subject);
|
|
338
|
+
}
|
|
339
|
+
return subject;
|
|
340
|
+
};
|
|
341
|
+
return async (request) => {
|
|
342
|
+
try {
|
|
343
|
+
const method = request.method;
|
|
344
|
+
if (!PROXY_METHODS.includes(method)) {
|
|
345
|
+
return new Response(null, {
|
|
346
|
+
status: 405,
|
|
347
|
+
headers: { Allow: PROXY_METHODS.join(", ") }
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
const resolved = await options.resolve(request);
|
|
351
|
+
if (resolved instanceof Response) return resolved;
|
|
352
|
+
if (typeof resolved?.user !== "string" || !resolved.user) {
|
|
353
|
+
return errorJson(401, "user_required", "Authentication required.");
|
|
354
|
+
}
|
|
355
|
+
const url = new URL(request.url);
|
|
356
|
+
const segments = routeSegments(url.pathname, options.basePath);
|
|
357
|
+
if (!segments) return errorJson(404, "route_not_allowed", "Not found.");
|
|
358
|
+
if (method !== "GET") {
|
|
359
|
+
const allowed = options.authorizeMutation ? await options.authorizeMutation(request) : request.headers.get("sec-fetch-site") !== "cross-site";
|
|
360
|
+
if (!allowed) return errorJson(403, "mutation_denied", "Request denied.");
|
|
361
|
+
}
|
|
362
|
+
const source = resolved.source ?? defaultSource;
|
|
363
|
+
let workspaceId;
|
|
364
|
+
if ("workspaceId" in resolved && !resolved.workspaceId && !resolved.tenant || "tenant" in resolved && !resolved.tenant && !resolved.workspaceId || resolved.workspaceId === "" || resolved.tenant === "") {
|
|
365
|
+
throw new TypeError(
|
|
366
|
+
"resolve returned an empty tenant or workspaceId. Return a non-empty id, or omit the key for one workspace per user."
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
if (chats === "isolated") {
|
|
370
|
+
if (resolved.workspaceId || !isFacade(target) || !target.workspaceIdFor) {
|
|
371
|
+
throw new TypeError(
|
|
372
|
+
'chats: "isolated" requires the Opengeni facade and a tenant or user resolution.'
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
workspaceId = await target.workspaceIdFor(
|
|
376
|
+
{ tenant: resolved.tenant, user: resolved.user, source },
|
|
377
|
+
{ isolation: "user" }
|
|
378
|
+
);
|
|
379
|
+
} else if (resolved.workspaceId) {
|
|
380
|
+
workspaceId = resolved.workspaceId;
|
|
381
|
+
} else if (resolved.tenant && isFacade(target)) {
|
|
382
|
+
workspaceId = await target.workspaceId({ tenant: resolved.tenant });
|
|
383
|
+
} else if (!("tenant" in resolved) && !("workspaceId" in resolved) && isFacade(target) && target.workspaceIdFor) {
|
|
384
|
+
workspaceId = await target.workspaceIdFor(
|
|
385
|
+
{ user: resolved.user, source },
|
|
386
|
+
{ isolation: "user" }
|
|
387
|
+
);
|
|
388
|
+
} else {
|
|
389
|
+
throw new TypeError(
|
|
390
|
+
"resolve must return a workspaceId (or a tenant or a user alone when given the Opengeni facade)."
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
const uploadsEnabled = filesEnabled && (resolved.visitor !== true || options.visitorUploads === true);
|
|
394
|
+
const client = service.asUser(resolved.user, { source });
|
|
395
|
+
const context = {
|
|
396
|
+
request,
|
|
397
|
+
workspaceId,
|
|
398
|
+
user: resolved.user,
|
|
399
|
+
source,
|
|
400
|
+
client
|
|
401
|
+
};
|
|
402
|
+
const call = { signal: request.signal };
|
|
403
|
+
const toolToken = async () => await mintToolToken({
|
|
404
|
+
audience: toolServer.url,
|
|
405
|
+
user: resolved.user,
|
|
406
|
+
tenant: resolved.tenant,
|
|
407
|
+
workspaceId,
|
|
408
|
+
source,
|
|
409
|
+
secret: toolServer.secret,
|
|
410
|
+
ttlSeconds: toolServer.ttlSeconds
|
|
411
|
+
});
|
|
412
|
+
const toolServerOwner = (sessionId2) => {
|
|
413
|
+
const key = `${workspaceId}\0${sessionId2}`;
|
|
414
|
+
let attached = toolSessions.get(key);
|
|
415
|
+
if (!attached) {
|
|
416
|
+
attached = client.getSession(workspaceId, sessionId2, call).then(
|
|
417
|
+
(session2) => (session2.mcpServers ?? []).some(
|
|
418
|
+
(server) => server.id === toolServer.id && server.url === toolServer.url
|
|
419
|
+
) ? session2.createdBy?.subjectId ?? null : null
|
|
420
|
+
);
|
|
421
|
+
attached.catch(() => toolSessions.delete(key));
|
|
422
|
+
if (toolSessions.size >= 1e3) toolSessions.delete(toolSessions.keys().next().value);
|
|
423
|
+
toolSessions.set(key, attached);
|
|
424
|
+
}
|
|
425
|
+
return attached;
|
|
426
|
+
};
|
|
427
|
+
const messageExtras = async (input) => {
|
|
428
|
+
const extras = options.beforeForwardMessage ? await options.beforeForwardMessage(input, context) : void 0;
|
|
429
|
+
if (extras instanceof Response || !toolServer || !input.sessionId) return extras;
|
|
430
|
+
const updates = extras?.mcpCredentialUpdates ?? [];
|
|
431
|
+
if (updates.some((update) => update.id === toolServer.id)) return extras;
|
|
432
|
+
const owner = await toolServerOwner(input.sessionId);
|
|
433
|
+
if (!owner || owner !== await subjectOf(client, `${source}\0${resolved.user}`)) {
|
|
434
|
+
return extras;
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
...extras,
|
|
438
|
+
mcpCredentialUpdates: [
|
|
439
|
+
...updates,
|
|
440
|
+
{
|
|
441
|
+
id: toolServer.id,
|
|
442
|
+
headers: { Authorization: `Bearer ${await toolToken()}` }
|
|
443
|
+
}
|
|
444
|
+
]
|
|
445
|
+
};
|
|
446
|
+
};
|
|
447
|
+
const refreshRealtimeCredentials = async (sessionId2, updates) => {
|
|
448
|
+
if (updates.length === 0) return;
|
|
449
|
+
try {
|
|
450
|
+
const servers = (await client.getSession(workspaceId, sessionId2, call)).mcpServers ?? [];
|
|
451
|
+
const rotations = updates.flatMap((update) => {
|
|
452
|
+
const server = servers.find((candidate) => candidate.id === update.id);
|
|
453
|
+
return server ? [
|
|
454
|
+
{
|
|
455
|
+
id: update.id,
|
|
456
|
+
expectedCredentialVersion: server.credentialVersion,
|
|
457
|
+
expectedServerUrl: server.url,
|
|
458
|
+
headers: update.headers
|
|
459
|
+
}
|
|
460
|
+
] : [];
|
|
461
|
+
});
|
|
462
|
+
if (rotations.length === 0) return;
|
|
463
|
+
await client.rotateSessionMcpCredentials(workspaceId, sessionId2, {
|
|
464
|
+
operationKey: crypto.randomUUID(),
|
|
465
|
+
updates: rotations
|
|
466
|
+
});
|
|
467
|
+
} catch (error) {
|
|
468
|
+
if (!(error instanceof OpenGeniApiError)) throw error;
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
const forwardMessage = async (value, input) => {
|
|
472
|
+
const message = sanitizeMessage(value, modelSelection || input.delivery === "submit");
|
|
473
|
+
const extras = await messageExtras(input);
|
|
474
|
+
if (extras instanceof Response) return extras;
|
|
475
|
+
const modelContext = joinContext(
|
|
476
|
+
extras?.modelContext,
|
|
477
|
+
typeof message.modelContext === "string" ? message.modelContext : void 0
|
|
478
|
+
);
|
|
479
|
+
return {
|
|
480
|
+
...message,
|
|
481
|
+
...modelContext ? { modelContext } : {},
|
|
482
|
+
...extras?.mcpCredentialUpdates?.length ? { mcpCredentialUpdates: extras.mcpCredentialUpdates } : {}
|
|
483
|
+
};
|
|
484
|
+
};
|
|
485
|
+
const forwardResponse = async (value, input) => {
|
|
486
|
+
const payload = browserPayload(value);
|
|
487
|
+
const extras = await messageExtras(input);
|
|
488
|
+
if (extras instanceof Response) return extras;
|
|
489
|
+
return {
|
|
490
|
+
...payload,
|
|
491
|
+
...extras?.mcpCredentialUpdates?.length ? { mcpCredentialUpdates: extras.mcpCredentialUpdates } : {}
|
|
492
|
+
};
|
|
493
|
+
};
|
|
494
|
+
const query = Object.fromEntries(url.searchParams);
|
|
495
|
+
const read = async (path) => json(await client.requestJson("GET", path, void 0, query, call));
|
|
496
|
+
const [root, ...rest] = segments;
|
|
497
|
+
if (root === "config") {
|
|
498
|
+
if (rest.length === 1 && rest[0] === "client" && method === "GET") {
|
|
499
|
+
const config = await client.requestJson(
|
|
500
|
+
"GET",
|
|
501
|
+
"/v1/config/client",
|
|
502
|
+
void 0,
|
|
503
|
+
{ ...query, workspaceId },
|
|
504
|
+
call
|
|
505
|
+
);
|
|
506
|
+
let artifacts = null;
|
|
507
|
+
if (editableLiveUrl) {
|
|
508
|
+
try {
|
|
509
|
+
artifacts = {
|
|
510
|
+
editableLiveUrl,
|
|
511
|
+
cachePartition: await cachePartition(
|
|
512
|
+
await getSessionProxyWorkspaceGrant(client, workspaceId, call),
|
|
513
|
+
workspaceId,
|
|
514
|
+
source
|
|
515
|
+
)
|
|
516
|
+
};
|
|
517
|
+
} catch (error) {
|
|
518
|
+
if (!(error instanceof OpenGeniApiError) || error.status !== 404) throw error;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
const {
|
|
522
|
+
artifacts: _upstreamArtifacts,
|
|
523
|
+
sessionUsageLimits: _upstreamUsageLimits,
|
|
524
|
+
...conversationConfig
|
|
525
|
+
} = config;
|
|
526
|
+
const upstreamVoice = config.voiceInput;
|
|
527
|
+
if (upstreamVoice && typeof upstreamVoice === "object") {
|
|
528
|
+
const { resumable: _resumable, ...voice } = upstreamVoice;
|
|
529
|
+
conversationConfig.voiceInput = voiceInputEnabled ? voice : { ...voice, available: false };
|
|
530
|
+
}
|
|
531
|
+
const upstreamUploads = config.fileUploads;
|
|
532
|
+
if (!uploadsEnabled) {
|
|
533
|
+
conversationConfig.fileUploads = {
|
|
534
|
+
...upstreamUploads && typeof upstreamUploads === "object" ? upstreamUploads : {},
|
|
535
|
+
enabled: false
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
return json({
|
|
539
|
+
...conversationConfig,
|
|
540
|
+
apiContractRevision: OPENGENI_API_CONTRACT_REVISION,
|
|
541
|
+
// The resolved workspace: a browser given only the proxy's baseUrl
|
|
542
|
+
// reads it here instead of knowing any Opengeni id.
|
|
543
|
+
workspaceId,
|
|
544
|
+
sandboxFiles: sandboxFilesEnabled,
|
|
545
|
+
// Stock UIs show Site previews and the artifact viewer only when
|
|
546
|
+
// this proxy serves them; `false` keeps them from failing on click.
|
|
547
|
+
artifacts: artifacts ?? false,
|
|
548
|
+
// Whether the stock "New chat" and "Archive" actions can succeed.
|
|
549
|
+
sessionCreation: options.createSession !== void 0,
|
|
550
|
+
archive: archiveEnabled,
|
|
551
|
+
// Explicit true also tells stock UIs to show the voice button, which
|
|
552
|
+
// is otherwise opt-in on the embedded conversation.
|
|
553
|
+
...realtimeVoiceEnabled ? options.realtimeVoice === true ? { realtimeVoice: true } : {} : { realtimeVoice: false },
|
|
554
|
+
// Explicit true also tells stock UIs to offer end users the model picker.
|
|
555
|
+
...modelSelection ? options.modelSelection === true ? { modelSelection: true } : {} : { modelSelection: false }
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
return errorJson(404, "route_not_allowed", "Not found.");
|
|
559
|
+
}
|
|
560
|
+
if (root !== "workspaces" || rest.length < 1) {
|
|
561
|
+
return errorJson(404, "route_not_allowed", "Not found.");
|
|
562
|
+
}
|
|
563
|
+
const [pathWorkspaceId, area, ...tail] = rest;
|
|
564
|
+
if (pathWorkspaceId !== workspaceId) {
|
|
565
|
+
return errorJson(403, "workspace_not_allowed", "This workspace is not available.");
|
|
566
|
+
}
|
|
567
|
+
const base = `/v1/workspaces/${workspaceId}`;
|
|
568
|
+
if (area === "usage" && tail.length === 1 && tail[0] === "me" && method === "GET") {
|
|
569
|
+
if (Object.keys(query).some((key) => key !== "period")) {
|
|
570
|
+
return errorJson(400, "invalid_usage_query", "Only period is accepted for own usage.");
|
|
571
|
+
}
|
|
572
|
+
return await read(`${base}/usage/me`);
|
|
573
|
+
}
|
|
574
|
+
if (area === void 0 && method === "GET") return await read(base);
|
|
575
|
+
if (area === "model-catalog" && tail.length === 0 && method === "GET") {
|
|
576
|
+
return await read(`${base}/model-catalog`);
|
|
577
|
+
}
|
|
578
|
+
if (area === "realtime-model-catalog" && tail.length === 0 && method === "GET") {
|
|
579
|
+
if (!realtimeVoiceEnabled) return errorJson(404, "route_not_allowed", "Not found.");
|
|
580
|
+
return await read(`${base}/realtime-model-catalog`);
|
|
581
|
+
}
|
|
582
|
+
if (area === "live-events" && tail.length === 1 && tail[0] === "stream" && method === "GET") {
|
|
583
|
+
await client.requestJson("GET", base, void 0, {}, call);
|
|
584
|
+
return workspaceLiveStream(client, workspaceId, url.searchParams, request, heartbeatMs);
|
|
585
|
+
}
|
|
586
|
+
if (area === "inference-control" && tail.length === 0 && method === "POST") {
|
|
587
|
+
const body2 = await readJsonBody(request, maxBodyBytes);
|
|
588
|
+
if (body2?.action !== "resume") {
|
|
589
|
+
reject(403, "control_not_allowed", "Only workspace resume is available.");
|
|
590
|
+
}
|
|
591
|
+
return json(await client.requestJson("POST", `${base}/inference-control`, body2));
|
|
592
|
+
}
|
|
593
|
+
if (area === "files" && filesEnabled && method === "POST") {
|
|
594
|
+
if (tail[0] === "uploads" && !uploadsEnabled) {
|
|
595
|
+
return errorJson(404, "route_not_allowed", "Not found.");
|
|
596
|
+
}
|
|
597
|
+
if (tail.length === 1 && tail[0] === "uploads") {
|
|
598
|
+
const body2 = await readJsonBody(request, maxBodyBytes);
|
|
599
|
+
return json(
|
|
600
|
+
await client.requestJson("POST", `${base}/files/uploads`, pick(body2, UPLOAD_FIELDS))
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
if (tail.length === 3 && tail[0] === "uploads" && tail[2] === "complete") {
|
|
604
|
+
return json(
|
|
605
|
+
await client.requestJson("POST", `${base}/files/uploads/${tail[1]}/complete`)
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
if (tail.length === 2 && tail[1] === "download-url") {
|
|
609
|
+
return json(
|
|
610
|
+
await client.requestJson(
|
|
611
|
+
"POST",
|
|
612
|
+
`${base}/files/${tail[0]}/download-url`,
|
|
613
|
+
void 0,
|
|
614
|
+
query,
|
|
615
|
+
call
|
|
616
|
+
)
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
return errorJson(404, "route_not_allowed", "Not found.");
|
|
620
|
+
}
|
|
621
|
+
if (area === "artifacts") {
|
|
622
|
+
const [artifactId, ...artifactOp] = tail;
|
|
623
|
+
const route2 = `${method} ${artifactOp.join("/")}`;
|
|
624
|
+
if (!filesEnabled || !artifactId || !SEGMENT.test(artifactId) || route2 !== "GET content" && route2 !== "POST playback-source") {
|
|
625
|
+
return errorJson(404, "route_not_allowed", "Not found.");
|
|
626
|
+
}
|
|
627
|
+
const sessionId2 = request.headers.get(SESSION_SCOPE_HEADER)?.trim() ?? "";
|
|
628
|
+
if (!SEGMENT.test(sessionId2)) {
|
|
629
|
+
reject(400, "session_scope_required", "Artifact reads must name their session.");
|
|
630
|
+
}
|
|
631
|
+
if (options.authorizeSession && !await options.authorizeSession(sessionId2, context)) {
|
|
632
|
+
return errorJson(404, "session_not_found", "Session not found.");
|
|
633
|
+
}
|
|
634
|
+
await getSessionProxyArtifactAssociation(
|
|
635
|
+
client,
|
|
636
|
+
workspaceId,
|
|
637
|
+
sessionId2,
|
|
638
|
+
"retained",
|
|
639
|
+
artifactId,
|
|
640
|
+
call
|
|
641
|
+
);
|
|
642
|
+
if (route2 === "POST playback-source") {
|
|
643
|
+
return json(
|
|
644
|
+
await client.requestJson(
|
|
645
|
+
"POST",
|
|
646
|
+
`${base}/artifacts/${artifactId}/playback-source`,
|
|
647
|
+
void 0,
|
|
648
|
+
{},
|
|
649
|
+
call
|
|
650
|
+
)
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
return retainedContent(
|
|
654
|
+
await client.getRetainedArtifactContent(workspaceId, artifactId, {
|
|
655
|
+
...retainedRange(request),
|
|
656
|
+
signal: request.signal
|
|
657
|
+
})
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
if (area === "transcriptions" && tail.length === 0 && method === "POST") {
|
|
661
|
+
if (!voiceInputEnabled) return errorJson(404, "route_not_allowed", "Not found.");
|
|
662
|
+
return json(await client.transcribeAudio(workspaceId, await transcriptionInput(request)));
|
|
663
|
+
}
|
|
664
|
+
if (area === "editable-artifacts" || area === "published-artifacts") {
|
|
665
|
+
if (area === "published-artifacts" && sitesEnabled && !request.headers.get(SESSION_SCOPE_HEADER)) {
|
|
666
|
+
const [siteId, ...siteOp] = tail;
|
|
667
|
+
const op2 = siteOp.join("/");
|
|
668
|
+
if (method !== "GET") return errorJson(404, "route_not_allowed", "Not found.");
|
|
669
|
+
if (siteId === void 0) {
|
|
670
|
+
return json(
|
|
671
|
+
await client.requestJson(
|
|
672
|
+
"GET",
|
|
673
|
+
`${base}/published-artifacts`,
|
|
674
|
+
void 0,
|
|
675
|
+
pick(query, SITE_LIST_FIELDS),
|
|
676
|
+
call
|
|
677
|
+
)
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
if (!SEGMENT.test(siteId) || op2 !== "" && op2 !== "content" && op2 !== "html") {
|
|
681
|
+
return errorJson(404, "route_not_allowed", "Not found.");
|
|
682
|
+
}
|
|
683
|
+
const versionId2 = query.versionId;
|
|
684
|
+
if (versionId2 !== void 0 && (typeof versionId2 !== "string" || !SEGMENT.test(versionId2))) {
|
|
685
|
+
reject(400, "version_invalid", "versionId is invalid.");
|
|
686
|
+
}
|
|
687
|
+
if (op2 === "html") {
|
|
688
|
+
if (typeof versionId2 !== "string") {
|
|
689
|
+
reject(400, "version_required", "versionId is required.");
|
|
690
|
+
}
|
|
691
|
+
const html2 = await downloadSessionProxySiteHtml(client, workspaceId, siteId, {
|
|
692
|
+
versionId: versionId2,
|
|
693
|
+
signal: request.signal
|
|
694
|
+
});
|
|
695
|
+
return new Response(boundedSiteHtml(html2.body, request.signal), {
|
|
696
|
+
headers: SITE_HTML_HEADERS
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
const item2 = `${base}/published-artifacts/${siteId}`;
|
|
700
|
+
return json(
|
|
701
|
+
await client.requestJson(
|
|
702
|
+
"GET",
|
|
703
|
+
op2 === "content" ? `${item2}/content` : item2,
|
|
704
|
+
void 0,
|
|
705
|
+
op2 === "content" && typeof versionId2 === "string" ? { versionId: versionId2 } : {},
|
|
706
|
+
call
|
|
707
|
+
)
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
if (!artifactsEnabled) return errorJson(404, "route_not_allowed", "Not found.");
|
|
711
|
+
const [artifactId, ...artifactOp] = tail;
|
|
712
|
+
const route2 = `${method} ${artifactOp.join("/")}`;
|
|
713
|
+
const editable = area === "editable-artifacts";
|
|
714
|
+
const allowed = editable ? route2 === "GET " || route2 === "POST live-ticket" : route2 === "GET " || route2 === "GET html";
|
|
715
|
+
if (!artifactId || !SEGMENT.test(artifactId) || !allowed) {
|
|
716
|
+
return errorJson(404, "route_not_allowed", "Not found.");
|
|
717
|
+
}
|
|
718
|
+
const sessionId2 = request.headers.get(SESSION_SCOPE_HEADER)?.trim() ?? "";
|
|
719
|
+
if (!SEGMENT.test(sessionId2)) {
|
|
720
|
+
reject(400, "session_scope_required", "Artifact reads must name their session.");
|
|
721
|
+
}
|
|
722
|
+
if (options.authorizeSession && !await options.authorizeSession(sessionId2, context)) {
|
|
723
|
+
return errorJson(404, "session_not_found", "Session not found.");
|
|
724
|
+
}
|
|
725
|
+
await getSessionProxyArtifactAssociation(
|
|
726
|
+
client,
|
|
727
|
+
workspaceId,
|
|
728
|
+
sessionId2,
|
|
729
|
+
editable ? "editable" : "site",
|
|
730
|
+
artifactId,
|
|
731
|
+
call
|
|
732
|
+
);
|
|
733
|
+
const item = `${base}/${area}/${artifactId}`;
|
|
734
|
+
if (route2 === "GET ") {
|
|
735
|
+
const replicaId = editable ? query.replicaId : void 0;
|
|
736
|
+
return json(
|
|
737
|
+
await client.requestJson(
|
|
738
|
+
"GET",
|
|
739
|
+
item,
|
|
740
|
+
void 0,
|
|
741
|
+
typeof replicaId === "string" ? { replicaId } : {},
|
|
742
|
+
call
|
|
743
|
+
)
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
if (route2 === "POST live-ticket") {
|
|
747
|
+
const body2 = await readJsonBody(request, maxBodyBytes);
|
|
748
|
+
return json(
|
|
749
|
+
await client.requestJson("POST", `${item}/live-ticket`, {
|
|
750
|
+
...pick(body2, TICKET_FIELDS),
|
|
751
|
+
sourceSessionId: sessionId2
|
|
752
|
+
}),
|
|
753
|
+
201
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
const versionId = query.versionId;
|
|
757
|
+
if (typeof versionId !== "string" || !SEGMENT.test(versionId)) {
|
|
758
|
+
reject(400, "version_required", "versionId is required.");
|
|
759
|
+
}
|
|
760
|
+
const html = await downloadSessionProxySiteHtml(client, workspaceId, artifactId, {
|
|
761
|
+
versionId,
|
|
762
|
+
signal: request.signal
|
|
763
|
+
});
|
|
764
|
+
return new Response(boundedSiteHtml(html.body, request.signal), {
|
|
765
|
+
headers: SITE_HTML_HEADERS
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
if (area !== "sessions") return errorJson(404, "route_not_allowed", "Not found.");
|
|
769
|
+
if (tail.length === 0 && method === "GET") {
|
|
770
|
+
if (!sessionList) return errorJson(404, "route_not_allowed", "Not found.");
|
|
771
|
+
if (query.view !== "page") {
|
|
772
|
+
reject(400, "page_view_required", "List sessions with listSessionPage.");
|
|
773
|
+
}
|
|
774
|
+
const listQuery = { ...query };
|
|
775
|
+
if (sessionList === "mine") {
|
|
776
|
+
listQuery.createdByKind = "subject";
|
|
777
|
+
listQuery.createdBySubjectId = await subjectOf(client, `${source}\0${resolved.user}`);
|
|
778
|
+
}
|
|
779
|
+
const page = await client.requestJson(
|
|
780
|
+
"GET",
|
|
781
|
+
`${base}/sessions`,
|
|
782
|
+
void 0,
|
|
783
|
+
listQuery,
|
|
784
|
+
call
|
|
785
|
+
);
|
|
786
|
+
return json(sessionList === "mine" ? { ...page, pinned: [] } : page);
|
|
787
|
+
}
|
|
788
|
+
if (tail.length === 0) {
|
|
789
|
+
if (method !== "POST" || !options.createSession) {
|
|
790
|
+
return errorJson(404, "route_not_allowed", "Not found.");
|
|
791
|
+
}
|
|
792
|
+
const input = createInput(await readJsonBody(request, maxBodyBytes), modelSelection);
|
|
793
|
+
if (input.startMode === "realtime" && !realtimeVoiceEnabled) {
|
|
794
|
+
return errorJson(404, "route_not_allowed", "Not found.");
|
|
795
|
+
}
|
|
796
|
+
const hooked = await options.createSession(input, context);
|
|
797
|
+
if (hooked instanceof Response) return hooked;
|
|
798
|
+
const created = withBrowserCreateChoices(
|
|
799
|
+
toolServer ? withToolServer(hooked, toolServer, await toolToken()) : hooked,
|
|
800
|
+
input
|
|
801
|
+
);
|
|
802
|
+
const extras = await messageExtras({
|
|
803
|
+
delivery: "create",
|
|
804
|
+
...input.startMode ? { startMode: input.startMode } : {}
|
|
805
|
+
});
|
|
806
|
+
if (extras instanceof Response) return extras;
|
|
807
|
+
const modelContext = joinContext(extras?.modelContext, created.modelContext);
|
|
808
|
+
return json(
|
|
809
|
+
await client.createSession(workspaceId, {
|
|
810
|
+
...created,
|
|
811
|
+
visibility: created.visibility ?? defaults.visibility,
|
|
812
|
+
agentAccess: created.agentAccess ?? defaults.agentAccess,
|
|
813
|
+
memoryScope: created.memoryScope ?? defaults.memoryScope,
|
|
814
|
+
...modelContext ? { modelContext } : {}
|
|
815
|
+
})
|
|
816
|
+
);
|
|
817
|
+
}
|
|
818
|
+
const [sessionId, ...op] = tail;
|
|
819
|
+
if (options.authorizeSession && !await options.authorizeSession(sessionId, context)) {
|
|
820
|
+
return errorJson(404, "session_not_found", "Session not found.");
|
|
821
|
+
}
|
|
822
|
+
const session = `${base}/sessions/${sessionId}`;
|
|
823
|
+
const route = `${method} ${op.join("/")}`;
|
|
824
|
+
const body = method === "GET" ? void 0 : await readJsonBody(request, maxBodyBytes, true);
|
|
825
|
+
const sanitize = (value) => sanitizeMessage(value, modelSelection);
|
|
826
|
+
const saveDraftPolicy = async (message) => {
|
|
827
|
+
if (modelSelection) return message;
|
|
828
|
+
const draft = await client.getComposerDraft(workspaceId, sessionId, call);
|
|
829
|
+
return {
|
|
830
|
+
...message,
|
|
831
|
+
model: draft.model,
|
|
832
|
+
reasoningEffort: draft.reasoningEffort,
|
|
833
|
+
latencyMode: draft.latencyMode
|
|
834
|
+
};
|
|
835
|
+
};
|
|
836
|
+
const forward = async (path, payload) => payload instanceof Response ? payload : json(await client.requestJson("POST", path, payload));
|
|
837
|
+
if (op[0] === "realtime") {
|
|
838
|
+
if (!realtimeVoiceEnabled) return errorJson(404, "route_not_allowed", "Not found.");
|
|
839
|
+
const realtime = realtimeRoute(method, op.slice(1));
|
|
840
|
+
if (!realtime) return errorJson(404, "route_not_allowed", "Not found.");
|
|
841
|
+
if (!body) reject(400, "invalid_body", "A JSON object body is required.");
|
|
842
|
+
const path = `${session}/${op.join("/")}`;
|
|
843
|
+
if (realtime === "begin") {
|
|
844
|
+
const extras = await messageExtras({
|
|
845
|
+
sessionId,
|
|
846
|
+
delivery: "realtime"
|
|
847
|
+
});
|
|
848
|
+
if (extras instanceof Response) return extras;
|
|
849
|
+
await refreshRealtimeCredentials(sessionId, extras?.mcpCredentialUpdates ?? []);
|
|
850
|
+
}
|
|
851
|
+
let payload = body;
|
|
852
|
+
if (realtime === "sync" && options.beforeForwardMessage && hasRealtimeMessages(body)) {
|
|
853
|
+
const extras = await options.beforeForwardMessage(
|
|
854
|
+
{ sessionId, delivery: "realtime" },
|
|
855
|
+
context
|
|
856
|
+
);
|
|
857
|
+
if (extras instanceof Response) return extras;
|
|
858
|
+
payload = withRealtimeContext(body, extras?.modelContext);
|
|
859
|
+
}
|
|
860
|
+
return json(await client.requestJson(method, path, payload));
|
|
861
|
+
}
|
|
862
|
+
switch (route) {
|
|
863
|
+
case "GET ":
|
|
864
|
+
return await read(session);
|
|
865
|
+
case "PUT archive": {
|
|
866
|
+
if (!archiveEnabled) return errorJson(404, "route_not_allowed", "Not found.");
|
|
867
|
+
const archived = body?.archived;
|
|
868
|
+
const expectedVersion = body?.expectedVersion;
|
|
869
|
+
if (typeof archived !== "boolean") reject(400, "invalid_body", "archived is required.");
|
|
870
|
+
return json(
|
|
871
|
+
await client.requestJson("PUT", `${session}/archive`, {
|
|
872
|
+
archived,
|
|
873
|
+
...typeof expectedVersion === "number" ? { expectedVersion } : {}
|
|
874
|
+
})
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
case "PATCH ": {
|
|
878
|
+
const title = body?.title;
|
|
879
|
+
if (typeof title !== "string" || Object.keys(body ?? {}).length !== 1) {
|
|
880
|
+
reject(400, "invalid_body", "Only { title } may be updated.");
|
|
881
|
+
}
|
|
882
|
+
return json(await client.requestJson("PATCH", session, { title }));
|
|
883
|
+
}
|
|
884
|
+
case "GET events":
|
|
885
|
+
return await listEvents(client, `${session}/events`, query, call);
|
|
886
|
+
case "GET events/stream":
|
|
887
|
+
await client.getSession(workspaceId, sessionId, call);
|
|
888
|
+
return proxySessionEventStream(client, workspaceId, sessionId, {
|
|
889
|
+
after: request,
|
|
890
|
+
signal: request.signal,
|
|
891
|
+
heartbeatMs
|
|
892
|
+
});
|
|
893
|
+
case "POST events": {
|
|
894
|
+
const event = clientEvent(body);
|
|
895
|
+
const payload = event.type === "user.message" ? await forwardMessage(event.payload, {
|
|
896
|
+
sessionId,
|
|
897
|
+
delivery: "send"
|
|
898
|
+
}) : await forwardResponse(event.payload, {
|
|
899
|
+
sessionId,
|
|
900
|
+
delivery: "send"
|
|
901
|
+
});
|
|
902
|
+
return await forward(
|
|
903
|
+
`${session}/events`,
|
|
904
|
+
payload instanceof Response ? payload : { ...event, payload }
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
case "POST steer":
|
|
908
|
+
return await forward(
|
|
909
|
+
`${session}/steer`,
|
|
910
|
+
await forwardMessage(body, { sessionId, delivery: "steer" })
|
|
911
|
+
);
|
|
912
|
+
case "GET queue":
|
|
913
|
+
return await read(`${session}/queue`);
|
|
914
|
+
case "GET composer-draft":
|
|
915
|
+
return await read(`${session}/composer-draft`);
|
|
916
|
+
case "PUT composer-draft":
|
|
917
|
+
return json(
|
|
918
|
+
await client.requestJson(
|
|
919
|
+
"PUT",
|
|
920
|
+
`${session}/composer-draft`,
|
|
921
|
+
await saveDraftPolicy(sanitize(body))
|
|
922
|
+
)
|
|
923
|
+
);
|
|
924
|
+
case "POST composer-draft/submit": {
|
|
925
|
+
const message = await forwardMessage(body, {
|
|
926
|
+
sessionId,
|
|
927
|
+
delivery: "submit"
|
|
928
|
+
});
|
|
929
|
+
return await forward(`${session}/composer-draft/submit`, message);
|
|
930
|
+
}
|
|
931
|
+
case "POST control": {
|
|
932
|
+
if (body?.action !== "pause" && body?.action !== "resume") {
|
|
933
|
+
reject(403, "control_not_allowed", "Only pause and resume are available.");
|
|
934
|
+
}
|
|
935
|
+
return json(await client.requestJson("POST", `${session}/control`, body));
|
|
936
|
+
}
|
|
937
|
+
case "GET human-input-requests":
|
|
938
|
+
return await read(`${session}/human-input-requests`);
|
|
939
|
+
case "GET goal":
|
|
940
|
+
return await read(`${session}/goal`);
|
|
941
|
+
case "PATCH goal": {
|
|
942
|
+
const status = body?.status;
|
|
943
|
+
if (status !== "paused" && status !== "active" || Object.keys(body ?? {}).some((key) => key !== "status" && key !== "rationale")) {
|
|
944
|
+
reject(
|
|
945
|
+
403,
|
|
946
|
+
"goal_update_not_allowed",
|
|
947
|
+
"Only { status: paused | active } is available."
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
return json(await client.requestJson("PATCH", `${session}/goal`, { status }));
|
|
951
|
+
}
|
|
952
|
+
case "DELETE goal":
|
|
953
|
+
await client.deleteGoal(workspaceId, sessionId);
|
|
954
|
+
return new Response(null, { status: 204 });
|
|
955
|
+
case "POST fs/read":
|
|
956
|
+
case "POST fs/read-workspace": {
|
|
957
|
+
if (!sandboxFilesEnabled) return errorJson(404, "route_not_allowed", "Not found.");
|
|
958
|
+
return json(
|
|
959
|
+
await client.requestJson("POST", `${session}/fs/read-workspace`, sandboxRead(body))
|
|
960
|
+
);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
if (op[0] === "artifacts" && (op.length === 2 || op[2] === "content") && method === "GET") {
|
|
964
|
+
if (!filesEnabled || op.length > 3) {
|
|
965
|
+
return errorJson(404, "route_not_allowed", "Not found.");
|
|
966
|
+
}
|
|
967
|
+
await client.getSession(workspaceId, sessionId, call);
|
|
968
|
+
if (op.length === 2) return await read(`${session}/artifacts/${op[1]}`);
|
|
969
|
+
return retainedContent(
|
|
970
|
+
await client.getSessionRetainedArtifactContent(workspaceId, sessionId, op[1], {
|
|
971
|
+
...retainedRange(request),
|
|
972
|
+
signal: request.signal
|
|
973
|
+
})
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
if (op.length === 2 && op[0] === "human-input-requests" && method === "GET") {
|
|
977
|
+
return await read(`${session}/human-input-requests/${op[1]}`);
|
|
978
|
+
}
|
|
979
|
+
if (op.length === 3 && op[0] === "queue" && QUEUE_OPERATIONS.has(op[2]) && method === "POST") {
|
|
980
|
+
return json(await client.requestJson("POST", `${session}/queue/${op[1]}/${op[2]}`, body));
|
|
981
|
+
}
|
|
982
|
+
return errorJson(404, "route_not_allowed", "Not found.");
|
|
983
|
+
} catch (error) {
|
|
984
|
+
return errorResponse(error, rejectedKeyNotice);
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
var PROXY_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
989
|
+
var UPLOAD_FIELDS = ["scope", "filename", "contentType", "sizeBytes", "sha256"];
|
|
990
|
+
var REALTIME_CONNECT_ROUTES = /* @__PURE__ */ new Set(["webrtc", "gateway", "supergrok"]);
|
|
991
|
+
var REALTIME_MESSAGE_KINDS = /* @__PURE__ */ new Set([
|
|
992
|
+
"delegation_call",
|
|
993
|
+
"user_transcript",
|
|
994
|
+
"assistant_transcript"
|
|
995
|
+
]);
|
|
996
|
+
function realtimeRoute(method, rest) {
|
|
997
|
+
if (rest.length === 0) return method === "POST" ? "begin" : null;
|
|
998
|
+
if (rest.length === 1) {
|
|
999
|
+
if (REALTIME_CONNECT_ROUTES.has(rest[0])) return method === "POST" ? "connect" : null;
|
|
1000
|
+
return method === "DELETE" ? "end" : null;
|
|
1001
|
+
}
|
|
1002
|
+
if (rest.length === 2 && rest[1] === "heartbeat") return method === "PATCH" ? "heartbeat" : null;
|
|
1003
|
+
if (rest.length === 2 && rest[1] === "sync") return method === "POST" ? "sync" : null;
|
|
1004
|
+
if (rest.length === 4 && rest[1] === "connections" && rest[3] === "activate") {
|
|
1005
|
+
return method === "POST" ? "activate" : null;
|
|
1006
|
+
}
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
1009
|
+
function hasRealtimeMessages(body) {
|
|
1010
|
+
return Array.isArray(body.entries) && body.entries.some(
|
|
1011
|
+
(entry) => entry !== null && typeof entry === "object" && REALTIME_MESSAGE_KINDS.has(entry.kind)
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
function withRealtimeContext(body, serverContext) {
|
|
1015
|
+
if (!serverContext?.trim() || !Array.isArray(body.entries)) return body;
|
|
1016
|
+
return {
|
|
1017
|
+
...body,
|
|
1018
|
+
entries: body.entries.map((entry) => {
|
|
1019
|
+
if (entry === null || typeof entry !== "object") return entry;
|
|
1020
|
+
const record = entry;
|
|
1021
|
+
if (!REALTIME_MESSAGE_KINDS.has(record.kind)) return entry;
|
|
1022
|
+
const modelContext = joinContext(
|
|
1023
|
+
serverContext,
|
|
1024
|
+
typeof record.modelContext === "string" ? record.modelContext : void 0
|
|
1025
|
+
);
|
|
1026
|
+
return modelContext ? { ...record, modelContext } : entry;
|
|
1027
|
+
})
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
function normalizeToolServer(toolServer) {
|
|
1031
|
+
const configured = toolServer.url ?? globalThis.process?.env?.OPENGENI_TOOL_SERVER_URL;
|
|
1032
|
+
let url;
|
|
1033
|
+
try {
|
|
1034
|
+
url = new URL(configured ?? "");
|
|
1035
|
+
} catch {
|
|
1036
|
+
throw new TypeError(
|
|
1037
|
+
"toolServer.url (or OPENGENI_TOOL_SERVER_URL) must be an absolute https:// URL."
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
if (url.protocol !== "https:") {
|
|
1041
|
+
throw new TypeError("toolServer.url must be an absolute https:// URL.");
|
|
1042
|
+
}
|
|
1043
|
+
const id = toolServer.id ?? "app";
|
|
1044
|
+
if (!/^[A-Za-z0-9_-]+$/.test(id)) {
|
|
1045
|
+
throw new TypeError("toolServer.id may contain only letters, digits, _ and -.");
|
|
1046
|
+
}
|
|
1047
|
+
const ask = toolServer.approvals?.ask;
|
|
1048
|
+
if (ask !== void 0 && ask !== true && !(Array.isArray(ask) && ask.every(isToolName))) {
|
|
1049
|
+
throw new TypeError("toolServer.approvals.ask must be true or a list of tool names.");
|
|
1050
|
+
}
|
|
1051
|
+
return {
|
|
1052
|
+
...toolServer,
|
|
1053
|
+
url: configured,
|
|
1054
|
+
id,
|
|
1055
|
+
secret: resolveToolTokenSecret(toolServer.secret)
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
function isToolName(value) {
|
|
1059
|
+
return typeof value === "string" && value.length > 0;
|
|
1060
|
+
}
|
|
1061
|
+
function withToolServer(created, toolServer, token) {
|
|
1062
|
+
const servers = created.mcpServers ?? [];
|
|
1063
|
+
if (servers.some((server) => server.id === toolServer.id)) {
|
|
1064
|
+
throw new TypeError(
|
|
1065
|
+
`createSession already attaches an MCP server with the toolServer id "${toolServer.id}".`
|
|
1066
|
+
);
|
|
1067
|
+
}
|
|
1068
|
+
const ask = toolServer.approvals?.ask;
|
|
1069
|
+
const tools = created.tools;
|
|
1070
|
+
return {
|
|
1071
|
+
...created,
|
|
1072
|
+
mcpServers: [
|
|
1073
|
+
...servers,
|
|
1074
|
+
{
|
|
1075
|
+
id: toolServer.id,
|
|
1076
|
+
...toolServer.name ? { name: toolServer.name } : {},
|
|
1077
|
+
url: toolServer.url,
|
|
1078
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
1079
|
+
...ask === true ? { requireApproval: true } : ask?.length ? { requireApproval: ask } : {}
|
|
1080
|
+
}
|
|
1081
|
+
],
|
|
1082
|
+
// Omitted tools keep workspace defaults; the attachment alone selects the
|
|
1083
|
+
// server. An explicit allow-list gets the server's tools on the first request.
|
|
1084
|
+
...Array.isArray(tools) && !tools.some((tool) => tool.id === toolServer.id) ? {
|
|
1085
|
+
tools: [...tools, { kind: "mcp", id: toolServer.id, eager: true }]
|
|
1086
|
+
} : {}
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
var EDITABLE_ARTIFACT_LIVE_PATH = "/v1/editable-artifacts/live";
|
|
1090
|
+
var SITE_LIST_FIELDS = ["limit", "cursor", "status"];
|
|
1091
|
+
var TICKET_FIELDS = [
|
|
1092
|
+
"replicaId",
|
|
1093
|
+
"modality",
|
|
1094
|
+
"liveProtocolVersion",
|
|
1095
|
+
"kernelVersion",
|
|
1096
|
+
"modelSchemaVersion",
|
|
1097
|
+
"snapshotVersion",
|
|
1098
|
+
"commandProtocolVersion",
|
|
1099
|
+
"committedTransactionProtocolVersion"
|
|
1100
|
+
];
|
|
1101
|
+
var SITE_HTML_HEADERS = {
|
|
1102
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
1103
|
+
"Cache-Control": "private, no-store",
|
|
1104
|
+
"Content-Security-Policy": "sandbox allow-scripts",
|
|
1105
|
+
"Cross-Origin-Resource-Policy": "same-origin",
|
|
1106
|
+
"X-Content-Type-Options": "nosniff",
|
|
1107
|
+
"Content-Disposition": 'attachment; filename="site.html"'
|
|
1108
|
+
};
|
|
1109
|
+
function liveSocketUrl(value) {
|
|
1110
|
+
const url = new URL(value);
|
|
1111
|
+
if (url.protocol === "https:") url.protocol = "wss:";
|
|
1112
|
+
else if (url.protocol === "http:") url.protocol = "ws:";
|
|
1113
|
+
if (url.protocol !== "wss:" && url.protocol !== "ws:") {
|
|
1114
|
+
throw new TypeError("editableLiveUrl must be an HTTP(S) or WS(S) URL");
|
|
1115
|
+
}
|
|
1116
|
+
return url.href;
|
|
1117
|
+
}
|
|
1118
|
+
function boundedSiteHtml(body, signal) {
|
|
1119
|
+
const reader = body?.getReader();
|
|
1120
|
+
let bytes = 0;
|
|
1121
|
+
let settled = false;
|
|
1122
|
+
let controller;
|
|
1123
|
+
const cleanup = () => signal.removeEventListener("abort", abort);
|
|
1124
|
+
const cancel = async (reason) => {
|
|
1125
|
+
if (settled) return;
|
|
1126
|
+
settled = true;
|
|
1127
|
+
cleanup();
|
|
1128
|
+
await reader?.cancel(reason).catch(() => void 0);
|
|
1129
|
+
reader?.releaseLock();
|
|
1130
|
+
};
|
|
1131
|
+
const abort = () => {
|
|
1132
|
+
if (settled) return;
|
|
1133
|
+
controller.error(signal.reason);
|
|
1134
|
+
void cancel(signal.reason);
|
|
1135
|
+
};
|
|
1136
|
+
return new ReadableStream(
|
|
1137
|
+
{
|
|
1138
|
+
start(value) {
|
|
1139
|
+
controller = value;
|
|
1140
|
+
if (signal.aborted) abort();
|
|
1141
|
+
else signal.addEventListener("abort", abort, { once: true });
|
|
1142
|
+
},
|
|
1143
|
+
async pull(value) {
|
|
1144
|
+
if (settled) return;
|
|
1145
|
+
try {
|
|
1146
|
+
const chunk = await reader?.read();
|
|
1147
|
+
if (settled) return;
|
|
1148
|
+
if (!chunk || chunk.done) {
|
|
1149
|
+
settled = true;
|
|
1150
|
+
cleanup();
|
|
1151
|
+
reader?.releaseLock();
|
|
1152
|
+
value.close();
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
bytes += chunk.value.byteLength;
|
|
1156
|
+
if (bytes > SESSION_PROXY_SITE_HTML_MAX_BYTES) {
|
|
1157
|
+
const error = new SessionProxySiteHtmlTooLargeError();
|
|
1158
|
+
value.error(error);
|
|
1159
|
+
await cancel(error);
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
value.enqueue(chunk.value);
|
|
1163
|
+
} catch (error) {
|
|
1164
|
+
if (settled) return;
|
|
1165
|
+
value.error(error);
|
|
1166
|
+
await cancel(error);
|
|
1167
|
+
}
|
|
1168
|
+
},
|
|
1169
|
+
cancel
|
|
1170
|
+
},
|
|
1171
|
+
// Read only when the downstream consumer requests a chunk.
|
|
1172
|
+
{ highWaterMark: 0 }
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
async function artifactViewerCapability(input) {
|
|
1176
|
+
return {
|
|
1177
|
+
editableLiveUrl: liveSocketUrl(
|
|
1178
|
+
input.editableLiveUrl ?? input.client.apiUrl(EDITABLE_ARTIFACT_LIVE_PATH)
|
|
1179
|
+
),
|
|
1180
|
+
cachePartition: await cachePartition(
|
|
1181
|
+
input.client.requestJson ? await getSessionProxyWorkspaceGrant(
|
|
1182
|
+
input.client,
|
|
1183
|
+
input.workspaceId
|
|
1184
|
+
) : await legacyViewerGrant(input.client, input.workspaceId),
|
|
1185
|
+
input.workspaceId,
|
|
1186
|
+
input.source ?? "default"
|
|
1187
|
+
)
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
async function cachePartition(grant, workspaceId, source) {
|
|
1191
|
+
if (grant.workspaceId !== workspaceId) {
|
|
1192
|
+
reject(403, "workspace_not_allowed", "This workspace is not available.");
|
|
1193
|
+
}
|
|
1194
|
+
const digest = await crypto.subtle.digest(
|
|
1195
|
+
"SHA-256",
|
|
1196
|
+
new TextEncoder().encode(
|
|
1197
|
+
JSON.stringify({
|
|
1198
|
+
source,
|
|
1199
|
+
subjectId: grant.subjectId,
|
|
1200
|
+
accountId: grant.accountId,
|
|
1201
|
+
workspaceId,
|
|
1202
|
+
permissions: [...grant.permissions].sort(),
|
|
1203
|
+
// Effective external grants carry live identity/link revisions. Include
|
|
1204
|
+
// them even when a revoke/regrant restores an identical permission set.
|
|
1205
|
+
externalActor: grant.metadata?.externalActor ?? null
|
|
1206
|
+
})
|
|
1207
|
+
)
|
|
1208
|
+
);
|
|
1209
|
+
return {
|
|
1210
|
+
accountId: grant.accountId,
|
|
1211
|
+
principalId: grant.subjectId,
|
|
1212
|
+
authorizationEpoch: `sha256:${[...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("")}`
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
async function legacyViewerGrant(client, workspaceId) {
|
|
1216
|
+
const access = await client.getAccessContext();
|
|
1217
|
+
const grant = access.workspaceGrants.find((candidate) => candidate.workspaceId === workspaceId);
|
|
1218
|
+
if (!grant) reject(403, "workspace_not_allowed", "This workspace is not available.");
|
|
1219
|
+
return grant;
|
|
1220
|
+
}
|
|
1221
|
+
function retainedRange(request) {
|
|
1222
|
+
const range = request.headers.get("range");
|
|
1223
|
+
if (range === null) return {};
|
|
1224
|
+
if (range.length > 128 || /[^\x20-\x7e]/.test(range)) {
|
|
1225
|
+
reject(400, "invalid_range", "Range must be at most 128 printable ASCII bytes.");
|
|
1226
|
+
}
|
|
1227
|
+
return { range };
|
|
1228
|
+
}
|
|
1229
|
+
function retainedContent(content) {
|
|
1230
|
+
return new Response(content.bytes, {
|
|
1231
|
+
status: content.status,
|
|
1232
|
+
headers: {
|
|
1233
|
+
"Content-Type": content.contentType,
|
|
1234
|
+
"Content-Length": String(content.contentLength),
|
|
1235
|
+
...content.contentRange ? { "Content-Range": content.contentRange } : {},
|
|
1236
|
+
"Accept-Ranges": "bytes",
|
|
1237
|
+
"Cache-Control": "private, no-store",
|
|
1238
|
+
// Bytes for the conversation's own reader; never rendered on this origin.
|
|
1239
|
+
"Content-Security-Policy": "sandbox",
|
|
1240
|
+
"Content-Disposition": "attachment",
|
|
1241
|
+
"Cross-Origin-Resource-Policy": "same-origin",
|
|
1242
|
+
"X-Content-Type-Options": "nosniff"
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
function sandboxRead(body) {
|
|
1247
|
+
const path = body?.path;
|
|
1248
|
+
const encoding = body?.encoding;
|
|
1249
|
+
const maxBytes = body?.maxBytes;
|
|
1250
|
+
if (typeof path !== "string" || !path || path.length > 4096) {
|
|
1251
|
+
reject(400, "invalid_body", "path is required.");
|
|
1252
|
+
}
|
|
1253
|
+
if (encoding !== void 0 && encoding !== "utf8" && encoding !== "base64") {
|
|
1254
|
+
reject(400, "invalid_body", "encoding must be utf8 or base64.");
|
|
1255
|
+
}
|
|
1256
|
+
if (maxBytes !== void 0 && (typeof maxBytes !== "number" || !Number.isSafeInteger(maxBytes) || maxBytes < 1 || maxBytes > 25 * 1024 * 1024)) {
|
|
1257
|
+
reject(400, "invalid_body", "maxBytes must be an integer from 1 to 26214400.");
|
|
1258
|
+
}
|
|
1259
|
+
return {
|
|
1260
|
+
path,
|
|
1261
|
+
workspaceOnly: true,
|
|
1262
|
+
...encoding === void 0 ? {} : { encoding },
|
|
1263
|
+
...maxBytes === void 0 ? {} : { maxBytes }
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
function routeSegments(pathname, basePath) {
|
|
1267
|
+
let rest;
|
|
1268
|
+
if (basePath !== void 0) {
|
|
1269
|
+
const prefix = basePath.replace(/\/+$/, "");
|
|
1270
|
+
if (!pathname.startsWith(`${prefix}/`)) return null;
|
|
1271
|
+
rest = pathname.slice(prefix.length);
|
|
1272
|
+
} else {
|
|
1273
|
+
const index = pathname.indexOf("/v1/");
|
|
1274
|
+
if (index < 0) return null;
|
|
1275
|
+
rest = pathname.slice(index);
|
|
1276
|
+
}
|
|
1277
|
+
const raw = rest.replace(/\/+$/, "").split("/").slice(1);
|
|
1278
|
+
if (raw[0] !== "v1" || raw.length < 2) return null;
|
|
1279
|
+
const segments = [];
|
|
1280
|
+
for (const part of raw.slice(1)) {
|
|
1281
|
+
let decoded;
|
|
1282
|
+
try {
|
|
1283
|
+
decoded = decodeURIComponent(part);
|
|
1284
|
+
} catch {
|
|
1285
|
+
return null;
|
|
1286
|
+
}
|
|
1287
|
+
if (!SEGMENT.test(decoded)) return null;
|
|
1288
|
+
segments.push(decoded);
|
|
1289
|
+
}
|
|
1290
|
+
return segments;
|
|
1291
|
+
}
|
|
1292
|
+
async function readJsonBody(request, maxBytes, optional = false) {
|
|
1293
|
+
const bytes = await readBoundedBytes(request, maxBytes);
|
|
1294
|
+
if (bytes.byteLength === 0) {
|
|
1295
|
+
if (optional) return void 0;
|
|
1296
|
+
reject(400, "invalid_body", "A JSON object body is required.");
|
|
1297
|
+
}
|
|
1298
|
+
const contentType = request.headers.get("content-type") ?? "";
|
|
1299
|
+
if (!/^application\/json\b/i.test(contentType)) {
|
|
1300
|
+
reject(415, "unsupported_media_type", "Send application/json.");
|
|
1301
|
+
}
|
|
1302
|
+
let parsed;
|
|
1303
|
+
try {
|
|
1304
|
+
parsed = JSON.parse(new TextDecoder().decode(bytes));
|
|
1305
|
+
} catch {
|
|
1306
|
+
reject(400, "invalid_body", "Body is not valid JSON.");
|
|
1307
|
+
}
|
|
1308
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1309
|
+
reject(400, "invalid_body", "A JSON object body is required.");
|
|
1310
|
+
}
|
|
1311
|
+
return parsed;
|
|
1312
|
+
}
|
|
1313
|
+
async function readBoundedBytes(request, maxBytes) {
|
|
1314
|
+
const declared = Number(request.headers.get("content-length") ?? "0");
|
|
1315
|
+
if (declared > maxBytes) reject(413, "body_too_large", "Request body is too large.");
|
|
1316
|
+
const reader = request.body?.getReader();
|
|
1317
|
+
const chunks = [];
|
|
1318
|
+
let total = 0;
|
|
1319
|
+
if (reader) {
|
|
1320
|
+
for (; ; ) {
|
|
1321
|
+
const { done, value } = await reader.read();
|
|
1322
|
+
if (done) break;
|
|
1323
|
+
total += value.byteLength;
|
|
1324
|
+
if (total > maxBytes) {
|
|
1325
|
+
void reader.cancel().catch(() => void 0);
|
|
1326
|
+
reject(413, "body_too_large", "Request body is too large.");
|
|
1327
|
+
}
|
|
1328
|
+
chunks.push(value);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
const bytes = new Uint8Array(total);
|
|
1332
|
+
let offset = 0;
|
|
1333
|
+
for (const chunk of chunks) {
|
|
1334
|
+
bytes.set(chunk, offset);
|
|
1335
|
+
offset += chunk.byteLength;
|
|
1336
|
+
}
|
|
1337
|
+
return bytes;
|
|
1338
|
+
}
|
|
1339
|
+
function pick(body, fields) {
|
|
1340
|
+
const picked = {};
|
|
1341
|
+
for (const field of fields) {
|
|
1342
|
+
if (body && Object.hasOwn(body, field)) picked[field] = body[field];
|
|
1343
|
+
}
|
|
1344
|
+
return picked;
|
|
1345
|
+
}
|
|
1346
|
+
function createInput(body, modelSelection) {
|
|
1347
|
+
for (const key of Object.keys(body ?? {})) {
|
|
1348
|
+
if (!CREATE_FIELDS.has(key) && !(modelSelection && isModelField(key))) {
|
|
1349
|
+
reject(
|
|
1350
|
+
400,
|
|
1351
|
+
"create_field_not_allowed",
|
|
1352
|
+
`The server chooses session configuration; "${key}" cannot be set from the browser.`
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
const initialMessage = body?.initialMessage;
|
|
1357
|
+
const idempotencyKey = body?.idempotencyKey;
|
|
1358
|
+
const startMode = body?.startMode;
|
|
1359
|
+
if (startMode !== void 0 && startMode !== "realtime") {
|
|
1360
|
+
reject(400, "invalid_start_mode", "startMode must be realtime when supplied.");
|
|
1361
|
+
}
|
|
1362
|
+
if (startMode === "realtime" && initialMessage !== void 0 && initialMessage !== "") {
|
|
1363
|
+
reject(
|
|
1364
|
+
400,
|
|
1365
|
+
"initial_message_not_allowed",
|
|
1366
|
+
"Voice-first creation does not send an initial message."
|
|
1367
|
+
);
|
|
1368
|
+
}
|
|
1369
|
+
if (startMode !== "realtime" && (typeof initialMessage !== "string" || !initialMessage.trim())) {
|
|
1370
|
+
reject(400, "initial_message_required", "initialMessage is required.");
|
|
1371
|
+
}
|
|
1372
|
+
if (idempotencyKey !== void 0 && (typeof idempotencyKey !== "string" || !idempotencyKey)) {
|
|
1373
|
+
reject(400, "invalid_idempotency_key", "idempotencyKey must be a non-empty string.");
|
|
1374
|
+
}
|
|
1375
|
+
const resources = createResources(body?.resources);
|
|
1376
|
+
if (startMode === "realtime" && resources.length > 0) {
|
|
1377
|
+
reject(400, "resource_not_allowed", "Keep attachments in the draft until a message is sent.");
|
|
1378
|
+
}
|
|
1379
|
+
const policy = {};
|
|
1380
|
+
for (const field of MODEL_FIELDS) {
|
|
1381
|
+
const value = body?.[field];
|
|
1382
|
+
if (value === void 0) continue;
|
|
1383
|
+
if (typeof value !== "string" || !value) {
|
|
1384
|
+
reject(400, "invalid_model_policy", `${field} must be a non-empty string.`);
|
|
1385
|
+
}
|
|
1386
|
+
policy[field] = value;
|
|
1387
|
+
}
|
|
1388
|
+
return {
|
|
1389
|
+
initialMessage: startMode === "realtime" ? "" : initialMessage,
|
|
1390
|
+
...startMode === "realtime" ? { startMode } : {},
|
|
1391
|
+
...idempotencyKey ? { idempotencyKey } : {},
|
|
1392
|
+
...resources.length > 0 ? { resources } : {},
|
|
1393
|
+
...policy
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
function isModelField(key) {
|
|
1397
|
+
return MODEL_FIELDS.includes(key);
|
|
1398
|
+
}
|
|
1399
|
+
function createResources(value) {
|
|
1400
|
+
if (value === void 0) return [];
|
|
1401
|
+
if (!Array.isArray(value)) {
|
|
1402
|
+
reject(403, "resource_not_allowed", "Only file attachments may be added from the browser.");
|
|
1403
|
+
}
|
|
1404
|
+
return value.map((resource) => {
|
|
1405
|
+
const file = resource;
|
|
1406
|
+
if (!file || typeof file !== "object" || file.kind !== "file" || typeof file.fileId !== "string" || !file.fileId || file.mountPath !== void 0 && typeof file.mountPath !== "string") {
|
|
1407
|
+
reject(403, "resource_not_allowed", "Only file attachments may be added from the browser.");
|
|
1408
|
+
}
|
|
1409
|
+
return {
|
|
1410
|
+
kind: "file",
|
|
1411
|
+
fileId: file.fileId,
|
|
1412
|
+
...file.mountPath !== void 0 ? { mountPath: file.mountPath } : {}
|
|
1413
|
+
};
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
function withBrowserCreateChoices(created, input) {
|
|
1417
|
+
const existing = created.resources ?? [];
|
|
1418
|
+
const attached = new Set(
|
|
1419
|
+
existing.flatMap((resource) => resource.kind === "file" ? [resource.fileId] : [])
|
|
1420
|
+
);
|
|
1421
|
+
const added = (input.resources ?? []).filter((resource) => !attached.has(resource.fileId));
|
|
1422
|
+
return {
|
|
1423
|
+
...created,
|
|
1424
|
+
// Preserve browser retry identity even when a simple host hook omits it;
|
|
1425
|
+
// an explicit server-owned key continues to take precedence.
|
|
1426
|
+
...created.idempotencyKey === void 0 && input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {},
|
|
1427
|
+
...input.startMode === "realtime" ? { startMode: "realtime", initialMessage: void 0 } : {},
|
|
1428
|
+
...added.length > 0 ? { resources: [...existing, ...added] } : {},
|
|
1429
|
+
...input.model ? { model: input.model } : {},
|
|
1430
|
+
...input.reasoningEffort ? { reasoningEffort: input.reasoningEffort } : {},
|
|
1431
|
+
...input.latencyMode ? { latencyMode: input.latencyMode } : {}
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
async function transcriptionInput(request) {
|
|
1435
|
+
const contentType = request.headers.get("content-type") ?? "";
|
|
1436
|
+
if (!/^multipart\/form-data\b/i.test(contentType)) {
|
|
1437
|
+
reject(415, "unsupported_media_type", "Send multipart/form-data.");
|
|
1438
|
+
}
|
|
1439
|
+
const bytes = await readBoundedBytes(request, MAX_TRANSCRIPTION_BODY_BYTES);
|
|
1440
|
+
let form;
|
|
1441
|
+
try {
|
|
1442
|
+
form = await new Response(bytes, {
|
|
1443
|
+
headers: { "Content-Type": contentType }
|
|
1444
|
+
}).formData();
|
|
1445
|
+
} catch {
|
|
1446
|
+
reject(400, "invalid_audio", "The recording could not be read.");
|
|
1447
|
+
}
|
|
1448
|
+
const audio = form.get("audio");
|
|
1449
|
+
const mimeType = form.get("mimeType");
|
|
1450
|
+
const duration = form.get("durationSeconds");
|
|
1451
|
+
if (!(audio instanceof File)) reject(400, "invalid_audio", "Audio file is required.");
|
|
1452
|
+
const durationSeconds = typeof duration === "string" && duration ? Number(duration) : void 0;
|
|
1453
|
+
if (durationSeconds !== void 0 && !(Number.isFinite(durationSeconds) && durationSeconds >= 0)) {
|
|
1454
|
+
reject(400, "invalid_audio", "durationSeconds must be a non-negative number.");
|
|
1455
|
+
}
|
|
1456
|
+
return {
|
|
1457
|
+
audio,
|
|
1458
|
+
mimeType: typeof mimeType === "string" && mimeType ? mimeType : audio.type,
|
|
1459
|
+
...durationSeconds !== void 0 ? { durationSeconds } : {},
|
|
1460
|
+
signal: request.signal
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
function browserPayload(value) {
|
|
1464
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1465
|
+
reject(400, "invalid_body", "A JSON object body is required.");
|
|
1466
|
+
}
|
|
1467
|
+
const input = { ...value };
|
|
1468
|
+
if (Object.hasOwn(input, "mcpCredentialUpdates")) {
|
|
1469
|
+
reject(403, "credential_update_not_allowed", "MCP credentials are server-owned.");
|
|
1470
|
+
}
|
|
1471
|
+
return input;
|
|
1472
|
+
}
|
|
1473
|
+
function sanitizeMessage(value, modelSelection) {
|
|
1474
|
+
const input = browserPayload(value);
|
|
1475
|
+
if (input.resources !== void 0) {
|
|
1476
|
+
if (!Array.isArray(input.resources) || input.resources.some(
|
|
1477
|
+
(resource) => !resource || typeof resource !== "object" || resource.kind !== "file"
|
|
1478
|
+
)) {
|
|
1479
|
+
reject(403, "resource_not_allowed", "Only file attachments may be added from the browser.");
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
if (!modelSelection) {
|
|
1483
|
+
for (const field of MODEL_FIELDS) delete input[field];
|
|
1484
|
+
}
|
|
1485
|
+
return input;
|
|
1486
|
+
}
|
|
1487
|
+
function clientEvent(body) {
|
|
1488
|
+
switch (body?.type) {
|
|
1489
|
+
case "user.message":
|
|
1490
|
+
case "user.approvalDecision":
|
|
1491
|
+
case "user.humanInputResponse":
|
|
1492
|
+
return body;
|
|
1493
|
+
default:
|
|
1494
|
+
return reject(403, "event_not_allowed", "This session event type is not available.");
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
function joinContext(...parts) {
|
|
1498
|
+
const joined = parts.map((part) => part?.trim()).filter(Boolean).join("\n\n");
|
|
1499
|
+
return joined || void 0;
|
|
1500
|
+
}
|
|
1501
|
+
async function listEvents(client, path, query, call) {
|
|
1502
|
+
if (query.mode === "forensic") {
|
|
1503
|
+
reject(403, "forensic_events_not_allowed", "Forensic event reads are not proxied.");
|
|
1504
|
+
}
|
|
1505
|
+
const upstream = await client.requestJsonResponse(path, query, call);
|
|
1506
|
+
const headers = {};
|
|
1507
|
+
upstream.headers.forEach((value, name) => {
|
|
1508
|
+
const lower = name.toLowerCase();
|
|
1509
|
+
if (lower.startsWith("x-opengeni-") && lower !== OPENGENI_API_CONTRACT_HEADER) {
|
|
1510
|
+
headers[name] = value;
|
|
1511
|
+
}
|
|
1512
|
+
});
|
|
1513
|
+
return new Response(await upstream.text(), {
|
|
1514
|
+
headers: {
|
|
1515
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
1516
|
+
"Cache-Control": "no-store",
|
|
1517
|
+
...headers
|
|
1518
|
+
}
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
function workspaceLiveStream(client, workspaceId, params, request, heartbeatMs) {
|
|
1522
|
+
const cursor = (name) => {
|
|
1523
|
+
const value = Number(params.get(name) ?? "0");
|
|
1524
|
+
return Number.isSafeInteger(value) && value > 0 ? value : 0;
|
|
1525
|
+
};
|
|
1526
|
+
const upstream = new AbortController();
|
|
1527
|
+
if (request.signal.aborted) upstream.abort();
|
|
1528
|
+
else
|
|
1529
|
+
request.signal.addEventListener("abort", () => upstream.abort(), {
|
|
1530
|
+
once: true
|
|
1531
|
+
});
|
|
1532
|
+
const events = client.streamWorkspaceLiveEvents(workspaceId, {
|
|
1533
|
+
controlAfter: cursor("controlAfter"),
|
|
1534
|
+
interactionAfter: cursor("interactionAfter"),
|
|
1535
|
+
signal: upstream.signal
|
|
1536
|
+
});
|
|
1537
|
+
const iterator = events[Symbol.asyncIterator]();
|
|
1538
|
+
const encoder = new TextEncoder();
|
|
1539
|
+
let heartbeat;
|
|
1540
|
+
const stop = () => {
|
|
1541
|
+
if (heartbeat !== void 0) clearInterval(heartbeat);
|
|
1542
|
+
heartbeat = void 0;
|
|
1543
|
+
};
|
|
1544
|
+
const body = new ReadableStream({
|
|
1545
|
+
start: (controller) => {
|
|
1546
|
+
heartbeat = setInterval(() => {
|
|
1547
|
+
try {
|
|
1548
|
+
controller.enqueue(encoder.encode(": ping\n\n"));
|
|
1549
|
+
} catch {
|
|
1550
|
+
stop();
|
|
1551
|
+
}
|
|
1552
|
+
}, heartbeatMs);
|
|
1553
|
+
},
|
|
1554
|
+
pull: async (controller) => {
|
|
1555
|
+
const next = await iterator.next().catch((error) => {
|
|
1556
|
+
stop();
|
|
1557
|
+
throw error;
|
|
1558
|
+
});
|
|
1559
|
+
if (next.done) {
|
|
1560
|
+
stop();
|
|
1561
|
+
controller.close();
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1564
|
+
const event = next.value;
|
|
1565
|
+
controller.enqueue(
|
|
1566
|
+
encoder.encode(
|
|
1567
|
+
`id: ${event.sequence}
|
|
1568
|
+
event: ${event.type}
|
|
1569
|
+
data: ${JSON.stringify(event)}
|
|
1570
|
+
|
|
1571
|
+
`
|
|
1572
|
+
)
|
|
1573
|
+
);
|
|
1574
|
+
},
|
|
1575
|
+
cancel: () => {
|
|
1576
|
+
stop();
|
|
1577
|
+
upstream.abort();
|
|
1578
|
+
void Promise.resolve(iterator.return?.(void 0)).catch(() => void 0);
|
|
1579
|
+
}
|
|
1580
|
+
});
|
|
1581
|
+
return new Response(body, {
|
|
1582
|
+
headers: {
|
|
1583
|
+
"Content-Type": "text/event-stream; charset=utf-8",
|
|
1584
|
+
"Cache-Control": "no-cache, no-transform",
|
|
1585
|
+
Connection: "keep-alive"
|
|
1586
|
+
}
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
function warnRejectedApiKey(error, notice) {
|
|
1590
|
+
if (notice.sent) return;
|
|
1591
|
+
notice.sent = true;
|
|
1592
|
+
console.warn(
|
|
1593
|
+
`[@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.`
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1596
|
+
function errorResponse(error, rejectedKeyNotice) {
|
|
1597
|
+
if (error instanceof ProxyRejection) return errorJson(error.status, error.code, error.message);
|
|
1598
|
+
if (error instanceof OpenGeniSetupError) {
|
|
1599
|
+
return json(
|
|
1600
|
+
{
|
|
1601
|
+
error: {
|
|
1602
|
+
code: error.code,
|
|
1603
|
+
message: error.message,
|
|
1604
|
+
retryable: false,
|
|
1605
|
+
...error.correlationId ? { requestId: error.correlationId } : {}
|
|
1606
|
+
}
|
|
1607
|
+
},
|
|
1608
|
+
error.status
|
|
1609
|
+
);
|
|
1610
|
+
}
|
|
1611
|
+
if (error instanceof OpenGeniApiError) {
|
|
1612
|
+
if (error.status === 401) warnRejectedApiKey(error, rejectedKeyNotice);
|
|
1613
|
+
const status = error.status >= 400 && error.status <= 599 ? error.status : 502;
|
|
1614
|
+
if (error.body) {
|
|
1615
|
+
return new Response(error.body, {
|
|
1616
|
+
status,
|
|
1617
|
+
headers: {
|
|
1618
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
1619
|
+
"Cache-Control": "no-store"
|
|
1620
|
+
}
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
return json(
|
|
1624
|
+
{
|
|
1625
|
+
error: {
|
|
1626
|
+
code: error.code ?? "opengeni_api_error",
|
|
1627
|
+
message: error.message,
|
|
1628
|
+
retryable: error.retryable,
|
|
1629
|
+
outcomeUnknown: error.outcomeUnknown,
|
|
1630
|
+
...error.correlationId ? { requestId: error.correlationId } : {},
|
|
1631
|
+
...error.details ? { details: error.details } : {}
|
|
1632
|
+
}
|
|
1633
|
+
},
|
|
1634
|
+
status
|
|
1635
|
+
);
|
|
1636
|
+
}
|
|
1637
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
1638
|
+
return errorJson(499, "aborted", "The request was aborted.");
|
|
1639
|
+
}
|
|
1640
|
+
console.error("[@opengeni/sdk] Session proxy request failed:", error);
|
|
1641
|
+
return errorJson(500, "proxy_error", "Request could not be completed.");
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
export {
|
|
1645
|
+
openGeniLinkScheme,
|
|
1646
|
+
isReservedOpenGeniLink,
|
|
1647
|
+
parseSandboxLink,
|
|
1648
|
+
parseRetainedFileReference,
|
|
1649
|
+
parseOpenGeniLink,
|
|
1650
|
+
openGeniConsolePath,
|
|
1651
|
+
SESSION_SCOPE_HEADER,
|
|
1652
|
+
formatSseEvent,
|
|
1653
|
+
sessionEventsToSseStream,
|
|
1654
|
+
sessionEventsToSseResponse,
|
|
1655
|
+
resumeSequenceFromRequest,
|
|
1656
|
+
proxySessionEventStream,
|
|
1657
|
+
SESSION_PROXY_SITE_HTML_MAX_BYTES,
|
|
1658
|
+
SessionProxySiteHtmlTooLargeError,
|
|
1659
|
+
getSessionProxyWorkspaceGrant,
|
|
1660
|
+
getSessionProxyArtifactAssociation,
|
|
1661
|
+
downloadSessionProxySiteHtml,
|
|
1662
|
+
createSessionProxyHandler,
|
|
1663
|
+
artifactViewerCapability
|
|
1664
|
+
};
|
|
1665
|
+
//# sourceMappingURL=chunk-6HZGAIUE.js.map
|