@opengeni/sdk 3.3.2 → 3.4.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 +1007 -72
- 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 +1369 -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-6GYGMLX4.js +332 -0
- package/dist/chunk-6GYGMLX4.js.map +1 -0
- package/dist/chunk-7EWT3PV6.js +368 -0
- package/dist/chunk-7EWT3PV6.js.map +1 -0
- package/dist/chunk-FTWVVPR6.js +1665 -0
- package/dist/chunk-FTWVVPR6.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-KGEABAID.js +71 -0
- package/dist/chunk-KGEABAID.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-JUS2UYRP.js → chunk-NCU5OAN7.js} +27 -2
- package/dist/chunk-NCU5OAN7.js.map +1 -0
- package/dist/{chunk-YXQX7RQT.js → chunk-OI5YLASH.js} +73 -2
- package/dist/chunk-OI5YLASH.js.map +1 -0
- package/dist/chunk-OV3POE62.js +359 -0
- package/dist/chunk-OV3POE62.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-TRNI7KEO.js → chunk-S2NLS3ES.js} +3669 -1281
- package/dist/chunk-S2NLS3ES.js.map +1 -0
- package/dist/chunk-UM4ZXXJR.js +9 -0
- package/dist/chunk-UM4ZXXJR.js.map +1 -0
- package/dist/{chunk-QT3KFVDP.js → chunk-UME3X7CP.js} +28 -32
- package/dist/chunk-UME3X7CP.js.map +1 -0
- package/dist/chunk-VT4YM4XQ.js +210 -0
- package/dist/chunk-VT4YM4XQ.js.map +1 -0
- package/dist/chunk-WSYYXONG.js +1602 -0
- package/dist/chunk-WSYYXONG.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-X5ITQVXE.js +15 -0
- package/dist/chunk-X5ITQVXE.js.map +1 -0
- package/dist/chunk-XA7HVIHI.js +126 -0
- package/dist/chunk-XA7HVIHI.js.map +1 -0
- package/dist/{chunk-KKRO2VCP.js → chunk-YJCHB7R3.js} +60 -12
- package/dist/chunk-YJCHB7R3.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 +1068 -118
- 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/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/external-vault-receiver.d.ts +203 -0
- package/dist/external-vault-receiver.js +564 -0
- package/dist/external-vault-receiver.js.map +1 -0
- 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 +62 -11
- package/dist/index.js +557 -338
- 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 +165 -71
- package/dist/interaction.js +2 -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 +1 -1
- 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 -28
- 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-ai.d.ts +35 -0
- package/dist/site-bridge-host.d.ts +112 -0
- package/dist/site-bridge-host.js +286 -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 +186 -0
- package/dist/site.js +63 -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 +2807 -583
- 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 +324 -0
- package/dist/vaults-api.d.ts +65 -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 +67 -0
- package/dist/workspace-integrations.js.map +1 -0
- package/dist/workspace-state.d.ts +8 -71
- package/package.json +103 -5
- 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 +5697 -1833
- 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/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/external-vault-receiver.ts +914 -0
- 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 +603 -65
- package/src/insights-usage.ts +64 -0
- package/src/interaction.ts +315 -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 +15 -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 +8 -82
- package/src/session-usage-limits.ts +71 -0
- package/src/shared-catalog-loader.ts +169 -0
- package/src/site-ai.ts +284 -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 +878 -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 +3170 -582
- package/src/usage-allowances.ts +189 -0
- package/src/vault-types.ts +379 -0
- package/src/vaults-api.ts +339 -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-C2H7HBNP.js.map +0 -1
- package/dist/chunk-C4DJPZRU.js +0 -119
- package/dist/chunk-C4DJPZRU.js.map +0 -1
- package/dist/chunk-JUS2UYRP.js.map +0 -1
- package/dist/chunk-KKRO2VCP.js.map +0 -1
- package/dist/chunk-QT3KFVDP.js.map +0 -1
- package/dist/chunk-TRNI7KEO.js.map +0 -1
- package/dist/chunk-VYCTL62C.js.map +0 -1
- package/dist/chunk-YXQX7RQT.js.map +0 -1
package/src/site-sql.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Site SQL for Site HTML: run the named, parameterized queries the Site's
|
|
3
|
+
* owner defined on its optional SQLite database. Sites never send SQL. The
|
|
4
|
+
* request travels over the host's Site HTTP channel like Site Data; the API
|
|
5
|
+
* checks who may run the query and binds `:viewer` (and the other `viewer_*`
|
|
6
|
+
* parameters) from the signed-in viewer, never from the page. Returned rows
|
|
7
|
+
* are untrusted input: render them with textContent, never innerHTML.
|
|
8
|
+
*/
|
|
9
|
+
import { SITE_DATA_PATH_PREFIX } from "./site-data";
|
|
10
|
+
|
|
11
|
+
export type OpenGeniSiteSqlErrorCode =
|
|
12
|
+
| "validation"
|
|
13
|
+
| "not_found"
|
|
14
|
+
| "forbidden"
|
|
15
|
+
/** This viewer may not do this on this Site (a role the Site's rules exclude). */
|
|
16
|
+
| "not_available_for_viewer"
|
|
17
|
+
| "conflict"
|
|
18
|
+
| "quota_exceeded"
|
|
19
|
+
| "rate_limited"
|
|
20
|
+
| "sql_error"
|
|
21
|
+
| "unavailable"
|
|
22
|
+
| "disabled"
|
|
23
|
+
| "site_sql_unsupported"
|
|
24
|
+
| "request_failed";
|
|
25
|
+
|
|
26
|
+
export class OpenGeniSiteSqlError extends Error {
|
|
27
|
+
constructor(
|
|
28
|
+
readonly code: OpenGeniSiteSqlErrorCode,
|
|
29
|
+
message: string,
|
|
30
|
+
readonly status: number,
|
|
31
|
+
readonly details: Record<string, unknown> = {},
|
|
32
|
+
) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.name = "OpenGeniSiteSqlError";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A parameter value. Booleans bind as 1 and 0. */
|
|
39
|
+
export type OpenGeniSiteSqlValue = string | number | boolean | null;
|
|
40
|
+
|
|
41
|
+
export type OpenGeniSiteSqlResult<Row extends Record<string, unknown> = Record<string, unknown>> = {
|
|
42
|
+
columns: string[];
|
|
43
|
+
/** Rows keyed by column name; blobs come back as `{ $base64: string }`. */
|
|
44
|
+
rows: Row[];
|
|
45
|
+
/** True when the query's row or size limit cut the result short. */
|
|
46
|
+
truncated: boolean;
|
|
47
|
+
rowsRead: number;
|
|
48
|
+
rowsWritten: number;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type OpenGeniSiteSql = {
|
|
52
|
+
/**
|
|
53
|
+
* Run the named query `name` with `params` (names without the colon). Do
|
|
54
|
+
* not pass `viewer` or any `viewer_*` parameter: the host binds them.
|
|
55
|
+
*/
|
|
56
|
+
run<Row extends Record<string, unknown> = Record<string, unknown>>(
|
|
57
|
+
name: string,
|
|
58
|
+
params?: Record<string, OpenGeniSiteSqlValue>,
|
|
59
|
+
): Promise<OpenGeniSiteSqlResult<Row>>;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const QUERY_NAME = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/u;
|
|
63
|
+
|
|
64
|
+
const KNOWN_CODES = new Set<string>([
|
|
65
|
+
"validation",
|
|
66
|
+
"not_found",
|
|
67
|
+
"forbidden",
|
|
68
|
+
"not_available_for_viewer",
|
|
69
|
+
"conflict",
|
|
70
|
+
"quota_exceeded",
|
|
71
|
+
"rate_limited",
|
|
72
|
+
"sql_error",
|
|
73
|
+
"unavailable",
|
|
74
|
+
"disabled",
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
type SiteSqlFetch = (path: string, init: RequestInit) => Promise<Response>;
|
|
78
|
+
|
|
79
|
+
/** Build `site.sql` over a Site HTTP fetch; `ready` says whether the host serves the pinned Site. */
|
|
80
|
+
export function createOpenGeniSiteSql(
|
|
81
|
+
fetchSite: SiteSqlFetch,
|
|
82
|
+
ready: () => Promise<boolean>,
|
|
83
|
+
): OpenGeniSiteSql {
|
|
84
|
+
return {
|
|
85
|
+
async run<Row extends Record<string, unknown> = Record<string, unknown>>(
|
|
86
|
+
name: string,
|
|
87
|
+
params: Record<string, OpenGeniSiteSqlValue> = {},
|
|
88
|
+
): Promise<OpenGeniSiteSqlResult<Row>> {
|
|
89
|
+
if (!QUERY_NAME.test(name))
|
|
90
|
+
throw new OpenGeniSiteSqlError("validation", "Invalid query name", 0);
|
|
91
|
+
if (Object.keys(params).some((key) => key === "viewer" || key.startsWith("viewer_")))
|
|
92
|
+
throw new OpenGeniSiteSqlError(
|
|
93
|
+
"validation",
|
|
94
|
+
"viewer parameters are bound by the host, not the Site",
|
|
95
|
+
0,
|
|
96
|
+
);
|
|
97
|
+
if (!(await ready()))
|
|
98
|
+
throw new OpenGeniSiteSqlError(
|
|
99
|
+
"site_sql_unsupported",
|
|
100
|
+
"This Site host does not provide Site SQL",
|
|
101
|
+
0,
|
|
102
|
+
);
|
|
103
|
+
const response = await fetchSite(
|
|
104
|
+
`${SITE_DATA_PATH_PREFIX}/sql/queries/${encodeURIComponent(name)}`,
|
|
105
|
+
{
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: { "content-type": "application/json" },
|
|
108
|
+
body: JSON.stringify({ params }),
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
const text = await response.text();
|
|
112
|
+
let value: unknown = null;
|
|
113
|
+
try {
|
|
114
|
+
value = text ? JSON.parse(text) : null;
|
|
115
|
+
} catch {
|
|
116
|
+
value = null;
|
|
117
|
+
}
|
|
118
|
+
if (response.ok) return value as OpenGeniSiteSqlResult<Row>;
|
|
119
|
+
throw siteSqlErrorFromResponse(response.status, value);
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function siteSqlErrorFromResponse(status: number, value: unknown): OpenGeniSiteSqlError {
|
|
125
|
+
const error =
|
|
126
|
+
typeof value === "object" && value !== null && "error" in value
|
|
127
|
+
? (value as { error: unknown }).error
|
|
128
|
+
: null;
|
|
129
|
+
const record =
|
|
130
|
+
typeof error === "object" && error !== null ? (error as Record<string, unknown>) : {};
|
|
131
|
+
const details =
|
|
132
|
+
typeof record.details === "object" && record.details !== null
|
|
133
|
+
? (record.details as Record<string, unknown>)
|
|
134
|
+
: {};
|
|
135
|
+
const detailCode = typeof details.code === "string" ? details.code : undefined;
|
|
136
|
+
const code: OpenGeniSiteSqlErrorCode =
|
|
137
|
+
detailCode && KNOWN_CODES.has(detailCode)
|
|
138
|
+
? (detailCode as OpenGeniSiteSqlErrorCode)
|
|
139
|
+
: status === 404
|
|
140
|
+
? "not_found"
|
|
141
|
+
: status === 403
|
|
142
|
+
? "forbidden"
|
|
143
|
+
: status === 413
|
|
144
|
+
? "quota_exceeded"
|
|
145
|
+
: status === 429
|
|
146
|
+
? "rate_limited"
|
|
147
|
+
: "request_failed";
|
|
148
|
+
const message =
|
|
149
|
+
typeof record.message === "string" && record.message
|
|
150
|
+
? record.message
|
|
151
|
+
: `Site SQL request failed with HTTP ${status}`;
|
|
152
|
+
return new OpenGeniSiteSqlError(code, message, status, details);
|
|
153
|
+
}
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SITE_FRAME_HEADER,
|
|
3
|
+
SITE_FRAME_ID_HEADER,
|
|
4
|
+
SITE_ID_HEADER,
|
|
5
|
+
SITE_VERSION_HEADER,
|
|
6
|
+
siteFrameBodyRefusal,
|
|
7
|
+
siteFrameOperation,
|
|
8
|
+
siteRequestHeaders,
|
|
9
|
+
} from "@opengeni/contracts/site-session-http";
|
|
10
|
+
import { OpenGeniApiError } from "./errors";
|
|
11
|
+
import { createSharedCatalogLoader } from "./shared-catalog-loader";
|
|
12
|
+
import { siteSessionPath, type SiteHttpRequest } from "./site-http";
|
|
13
|
+
import type { OpenGeniSiteToolCatalog } from "./site";
|
|
14
|
+
import type { OpenGeniWorkspaceTools } from "./tools";
|
|
15
|
+
import type {
|
|
16
|
+
ToolGatewayCallRequest,
|
|
17
|
+
ToolGatewayCallResponse,
|
|
18
|
+
ToolGatewayIdentity,
|
|
19
|
+
ToolGatewayResolveRequest,
|
|
20
|
+
ToolGatewayResolvedTool,
|
|
21
|
+
ToolGatewayInvokeRequest,
|
|
22
|
+
ToolGatewayInvokeResponse,
|
|
23
|
+
} from "./types";
|
|
24
|
+
|
|
25
|
+
export type SiteToolCallRequest = ToolGatewayCallRequest & {
|
|
26
|
+
siteArtifactId: string;
|
|
27
|
+
siteVersionId: string;
|
|
28
|
+
};
|
|
29
|
+
export type SiteToolCaller = (input: {
|
|
30
|
+
workspaceId: string;
|
|
31
|
+
request: ToolGatewayCallRequest &
|
|
32
|
+
Partial<Pick<SiteToolCallRequest, "siteArtifactId" | "siteVersionId">>;
|
|
33
|
+
signal: AbortSignal;
|
|
34
|
+
}) => Promise<ToolGatewayCallResponse>;
|
|
35
|
+
export type SiteToolBridge = {
|
|
36
|
+
resolve?: (
|
|
37
|
+
request: ToolGatewayResolveRequest,
|
|
38
|
+
options: { signal: AbortSignal },
|
|
39
|
+
) => Promise<ToolGatewayResolvedTool>;
|
|
40
|
+
invoke?: (
|
|
41
|
+
request: ToolGatewayInvokeRequest,
|
|
42
|
+
options: { signal: AbortSignal },
|
|
43
|
+
) => Promise<ToolGatewayInvokeResponse>;
|
|
44
|
+
fetch?: (request: SiteHttpRequest, signal: AbortSignal) => Promise<Response>;
|
|
45
|
+
/** The host serves Site Data for its pinned published Site through `fetch`. */
|
|
46
|
+
siteData?: boolean;
|
|
47
|
+
catalog: (options: { signal: AbortSignal }) => Promise<OpenGeniSiteToolCatalog>;
|
|
48
|
+
call: (
|
|
49
|
+
request: ToolGatewayCallRequest,
|
|
50
|
+
options: { signal: AbortSignal },
|
|
51
|
+
) => Promise<ToolGatewayCallResponse>;
|
|
52
|
+
};
|
|
53
|
+
export type CreateSiteToolBridgeOptions = {
|
|
54
|
+
workspaceTools: Pick<OpenGeniWorkspaceTools, "$catalog"> &
|
|
55
|
+
Partial<Pick<OpenGeniWorkspaceTools, "$targetProtocol" | "$resolve" | "$invoke" | "$manifest">>;
|
|
56
|
+
workspaceId: string;
|
|
57
|
+
|
|
58
|
+
callTool: SiteToolCaller;
|
|
59
|
+
isCatalogStale?: (error: unknown) => boolean;
|
|
60
|
+
/** Optional authenticated host transport for the host-bound workspace API.
|
|
61
|
+
* Omit to expose tools only. Site-provided authorization headers are never forwarded. */
|
|
62
|
+
fetchResponse?: (path: string, init: RequestInit) => Promise<Response>;
|
|
63
|
+
} & (
|
|
64
|
+
| { artifactId: string; siteVersionId: string; requestedTools: readonly ToolGatewayIdentity[] }
|
|
65
|
+
| { artifactId?: never; siteVersionId?: never; requestedTools?: never }
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
/** Host-side bridge for the opaque Site frame. Supply an authenticated transport;
|
|
69
|
+
* the server independently checks live viewer access and the exact Site version.
|
|
70
|
+
* Recreate on actor/version change. Never take these pinned fields from HTML.
|
|
71
|
+
*
|
|
72
|
+
* `fetch` forwards only the `SITE_OPERATIONS` allowlist (`siteSessionPath`);
|
|
73
|
+
* every other route is refused before a request is made. Each bridge is one
|
|
74
|
+
* frame: it mints a frame id, so a frame without a published Site reaches only
|
|
75
|
+
* the sessions it started. */
|
|
76
|
+
export function createSiteToolBridge(input: CreateSiteToolBridgeOptions): SiteToolBridge {
|
|
77
|
+
const allowed = input.requestedTools ? new Set(input.requestedTools.map(identityKey)) : null;
|
|
78
|
+
const frameId = crypto.randomUUID();
|
|
79
|
+
const targeted =
|
|
80
|
+
input.workspaceTools.$targetProtocol === 1 &&
|
|
81
|
+
input.workspaceTools.$resolve &&
|
|
82
|
+
input.workspaceTools.$invoke &&
|
|
83
|
+
input.workspaceTools.$manifest;
|
|
84
|
+
const context = input.artifactId
|
|
85
|
+
? { siteArtifactId: input.artifactId, siteVersionId: input.siteVersionId }
|
|
86
|
+
: {};
|
|
87
|
+
const manifests = new Map<string, readonly ToolGatewayResolvedTool[]>();
|
|
88
|
+
const resolve = async (
|
|
89
|
+
request: ToolGatewayResolveRequest,
|
|
90
|
+
{ signal }: { signal: AbortSignal },
|
|
91
|
+
) => {
|
|
92
|
+
if (
|
|
93
|
+
allowed &&
|
|
94
|
+
"identity" in request.target &&
|
|
95
|
+
!allowed.has(identityKey(request.target.identity))
|
|
96
|
+
)
|
|
97
|
+
throw new Error("This tool is not available to the Site");
|
|
98
|
+
const tool = await input.workspaceTools.$resolve!(request.target, { signal, ...context });
|
|
99
|
+
if (allowed && !allowed.has(identityKey(tool.entry.identity)))
|
|
100
|
+
throw new Error("This tool is not available to the Site");
|
|
101
|
+
return tool;
|
|
102
|
+
};
|
|
103
|
+
const invoke = async (request: ToolGatewayInvokeRequest, { signal }: { signal: AbortSignal }) => {
|
|
104
|
+
if (
|
|
105
|
+
allowed &&
|
|
106
|
+
"identity" in request.target &&
|
|
107
|
+
!allowed.has(identityKey(request.target.identity))
|
|
108
|
+
)
|
|
109
|
+
throw new Error("This tool is not available to the Site");
|
|
110
|
+
return await input.workspaceTools.$invoke!(
|
|
111
|
+
{
|
|
112
|
+
target: request.target,
|
|
113
|
+
operationId: request.operationId,
|
|
114
|
+
arguments: request.arguments,
|
|
115
|
+
...(request.expectedDefinitionDigest === undefined
|
|
116
|
+
? {}
|
|
117
|
+
: { expectedDefinitionDigest: request.expectedDefinitionDigest }),
|
|
118
|
+
...context,
|
|
119
|
+
},
|
|
120
|
+
{ signal },
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
// One shared load serves every concurrent Site request (cancelled only when
|
|
124
|
+
// all of them abort); a stale rejection reloads once for all requests
|
|
125
|
+
// rejected on that digest.
|
|
126
|
+
const projectedCatalog = createSharedCatalogLoader<OpenGeniSiteToolCatalog>(
|
|
127
|
+
async (refresh, signal) => {
|
|
128
|
+
if (targeted && input.requestedTools) {
|
|
129
|
+
const manifest = await input.workspaceTools.$manifest!(
|
|
130
|
+
{ identities: [...input.requestedTools], ...context },
|
|
131
|
+
{ signal },
|
|
132
|
+
);
|
|
133
|
+
signal.throwIfAborted();
|
|
134
|
+
if (manifests.size >= 4) manifests.delete(manifests.keys().next().value!);
|
|
135
|
+
manifests.set(manifest.digest, manifest.tools);
|
|
136
|
+
return {
|
|
137
|
+
version: 1,
|
|
138
|
+
generation: 1,
|
|
139
|
+
digest: manifest.digest,
|
|
140
|
+
createdAt: new Date().toISOString(),
|
|
141
|
+
entries: manifest.tools.map((tool) => tool.entry),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const current = await input.workspaceTools.$catalog({
|
|
145
|
+
signal,
|
|
146
|
+
...(refresh ? { refresh } : {}),
|
|
147
|
+
});
|
|
148
|
+
if (current.workspaceId !== input.workspaceId)
|
|
149
|
+
throw new Error("Site catalog workspace mismatch");
|
|
150
|
+
return {
|
|
151
|
+
version: current.version,
|
|
152
|
+
generation: current.generation,
|
|
153
|
+
digest: current.digest,
|
|
154
|
+
createdAt: current.createdAt,
|
|
155
|
+
entries: current.entries.filter(
|
|
156
|
+
(entry) => !allowed || allowed.has(identityKey(entry.identity)),
|
|
157
|
+
),
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
);
|
|
161
|
+
const loadCatalog = async ({
|
|
162
|
+
signal,
|
|
163
|
+
staleDigest,
|
|
164
|
+
}: {
|
|
165
|
+
signal: AbortSignal;
|
|
166
|
+
staleDigest?: string;
|
|
167
|
+
}): Promise<OpenGeniSiteToolCatalog> => {
|
|
168
|
+
signal.throwIfAborted();
|
|
169
|
+
const catalog =
|
|
170
|
+
staleDigest === undefined
|
|
171
|
+
? await projectedCatalog.load({ signal })
|
|
172
|
+
: await projectedCatalog.reloadAfterStale(staleDigest, signal);
|
|
173
|
+
signal.throwIfAborted();
|
|
174
|
+
return catalog;
|
|
175
|
+
};
|
|
176
|
+
return {
|
|
177
|
+
...(targeted ? { resolve, invoke } : {}),
|
|
178
|
+
...(input.fetchResponse && input.artifactId ? { siteData: true } : {}),
|
|
179
|
+
...(input.fetchResponse
|
|
180
|
+
? {
|
|
181
|
+
fetch: async (message: SiteHttpRequest, signal: AbortSignal) => {
|
|
182
|
+
const path = siteSessionPath(
|
|
183
|
+
message.path,
|
|
184
|
+
input.workspaceId,
|
|
185
|
+
message.method,
|
|
186
|
+
input.artifactId,
|
|
187
|
+
);
|
|
188
|
+
// Site code sends message text, not approvals, answers, resources,
|
|
189
|
+
// credentials or connection choices. The API refuses them too.
|
|
190
|
+
const refused = siteFrameBodyFieldRefusal(path, message, input);
|
|
191
|
+
if (refused) return refused;
|
|
192
|
+
const headers = siteRequestHeaders(message.headers);
|
|
193
|
+
// Host-set marker: the API treats every bridged request as a Site frame,
|
|
194
|
+
// limited to SITE_OPERATIONS and to this frame's own sessions.
|
|
195
|
+
headers.set(SITE_FRAME_HEADER, "1");
|
|
196
|
+
headers.set(SITE_FRAME_ID_HEADER, frameId);
|
|
197
|
+
if (input.artifactId) {
|
|
198
|
+
headers.set(SITE_ID_HEADER, input.artifactId);
|
|
199
|
+
headers.set(SITE_VERSION_HEADER, input.siteVersionId);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return input.fetchResponse!(path, {
|
|
203
|
+
method: message.method,
|
|
204
|
+
signal,
|
|
205
|
+
headers: Object.fromEntries(headers),
|
|
206
|
+
...(message.body === undefined ? {} : { body: message.body }),
|
|
207
|
+
});
|
|
208
|
+
},
|
|
209
|
+
}
|
|
210
|
+
: {}),
|
|
211
|
+
catalog: async ({ signal }) => await loadCatalog({ signal }),
|
|
212
|
+
call: async (request, { signal }) => {
|
|
213
|
+
if (allowed && !allowed.has(identityKey(request.identity)))
|
|
214
|
+
throw new Error("This tool is not available to the Site");
|
|
215
|
+
if (targeted && input.requestedTools) {
|
|
216
|
+
const tool = manifests
|
|
217
|
+
.get(request.catalogDigest)
|
|
218
|
+
?.find(
|
|
219
|
+
(candidate) => identityKey(candidate.entry.identity) === identityKey(request.identity),
|
|
220
|
+
);
|
|
221
|
+
if (!tool)
|
|
222
|
+
throw new OpenGeniApiError(
|
|
223
|
+
409,
|
|
224
|
+
JSON.stringify({
|
|
225
|
+
error: { code: "conflict", details: { code: "catalog_stale" }, retryable: true },
|
|
226
|
+
}),
|
|
227
|
+
);
|
|
228
|
+
try {
|
|
229
|
+
const response = await invoke(
|
|
230
|
+
{
|
|
231
|
+
operationId: request.operationId ?? crypto.randomUUID(),
|
|
232
|
+
target: { identity: request.identity },
|
|
233
|
+
arguments: request.arguments,
|
|
234
|
+
expectedDefinitionDigest: tool.definitionDigest,
|
|
235
|
+
},
|
|
236
|
+
{ signal },
|
|
237
|
+
);
|
|
238
|
+
return {
|
|
239
|
+
operationId: response.operationId,
|
|
240
|
+
catalogDigest: request.catalogDigest,
|
|
241
|
+
result: legacySiteToolResult(response.result),
|
|
242
|
+
};
|
|
243
|
+
} catch (error) {
|
|
244
|
+
if (
|
|
245
|
+
error instanceof OpenGeniApiError &&
|
|
246
|
+
error.retryable !== false &&
|
|
247
|
+
!error.outcomeUnknown &&
|
|
248
|
+
error.details?.code === "tool_definition_stale"
|
|
249
|
+
) {
|
|
250
|
+
projectedCatalog.invalidate(request.catalogDigest);
|
|
251
|
+
manifests.delete(request.catalogDigest);
|
|
252
|
+
throw new OpenGeniApiError(
|
|
253
|
+
409,
|
|
254
|
+
JSON.stringify({
|
|
255
|
+
error: {
|
|
256
|
+
code: "catalog_stale",
|
|
257
|
+
details: { code: "catalog_stale" },
|
|
258
|
+
retryable: true,
|
|
259
|
+
},
|
|
260
|
+
}),
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
let usedDigest: string | undefined;
|
|
267
|
+
const call = async (staleDigest?: string) => {
|
|
268
|
+
const catalog = await loadCatalog({
|
|
269
|
+
signal,
|
|
270
|
+
...(staleDigest === undefined ? {} : { staleDigest }),
|
|
271
|
+
});
|
|
272
|
+
usedDigest = catalog.digest;
|
|
273
|
+
if (
|
|
274
|
+
!catalog.entries.some(
|
|
275
|
+
(entry) => identityKey(entry.identity) === identityKey(request.identity),
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
throw new Error("This requested tool is not enabled in the workspace");
|
|
279
|
+
const response = await input.callTool({
|
|
280
|
+
workspaceId: input.workspaceId,
|
|
281
|
+
signal,
|
|
282
|
+
request: {
|
|
283
|
+
...(request.operationId ? { operationId: request.operationId } : {}),
|
|
284
|
+
catalogDigest: catalog.digest,
|
|
285
|
+
identity: request.identity,
|
|
286
|
+
arguments: request.arguments,
|
|
287
|
+
...(input.artifactId
|
|
288
|
+
? { siteArtifactId: input.artifactId, siteVersionId: input.siteVersionId }
|
|
289
|
+
: {}),
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
return { ...response, result: legacySiteToolResult(response.result) };
|
|
293
|
+
};
|
|
294
|
+
try {
|
|
295
|
+
return await call();
|
|
296
|
+
} catch (error) {
|
|
297
|
+
// Only pre-execution catalog rejection permits one retry. Never retry
|
|
298
|
+
// transport failures, expired credentials or uncertain tool effects.
|
|
299
|
+
if (
|
|
300
|
+
staleRetryVetoed(error) ||
|
|
301
|
+
!(input.isCatalogStale ?? isSiteCatalogStaleError)(error) ||
|
|
302
|
+
usedDigest === undefined
|
|
303
|
+
)
|
|
304
|
+
throw error;
|
|
305
|
+
return await call(usedDigest);
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Saved Site bundles may treat a provider's error.code as retry authorization.
|
|
312
|
+
* An executed result is data, never a pre-execution control signal. Only wrap
|
|
313
|
+
* colliding error results on the legacy call path; keep the original diagnostic
|
|
314
|
+
* intact and do not project request arguments, tokens, or host credentials. */
|
|
315
|
+
function legacySiteToolResult(
|
|
316
|
+
result: ToolGatewayCallResponse["result"],
|
|
317
|
+
): ToolGatewayCallResponse["result"] {
|
|
318
|
+
if (!result?.isError) return result;
|
|
319
|
+
const structured = result.structuredContent;
|
|
320
|
+
const error = structured?.error;
|
|
321
|
+
if (
|
|
322
|
+
typeof error !== "object" ||
|
|
323
|
+
error === null ||
|
|
324
|
+
!("code" in error) ||
|
|
325
|
+
(error.code !== "catalog_stale" && error.code !== "tool_definition_stale")
|
|
326
|
+
)
|
|
327
|
+
return result;
|
|
328
|
+
return {
|
|
329
|
+
...result,
|
|
330
|
+
structuredContent: {
|
|
331
|
+
...structured,
|
|
332
|
+
error: {
|
|
333
|
+
code: "site_tool_execution_failed",
|
|
334
|
+
message:
|
|
335
|
+
"message" in error && typeof error.message === "string"
|
|
336
|
+
? error.message
|
|
337
|
+
: "Site tool execution failed",
|
|
338
|
+
retryable: false,
|
|
339
|
+
outcomeUnknown: "outcomeUnknown" in error && error.outcomeUnknown === true,
|
|
340
|
+
providerError: error,
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function isSiteCatalogStaleError(error: unknown): boolean {
|
|
347
|
+
return (
|
|
348
|
+
!staleRetryVetoed(error) &&
|
|
349
|
+
error instanceof OpenGeniApiError &&
|
|
350
|
+
error.status === 409 &&
|
|
351
|
+
error.details?.code === "catalog_stale"
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
function staleRetryVetoed(error: unknown): boolean {
|
|
355
|
+
return (
|
|
356
|
+
typeof error === "object" &&
|
|
357
|
+
error !== null &&
|
|
358
|
+
(("outcomeUnknown" in error && error.outcomeUnknown === true) ||
|
|
359
|
+
("retryable" in error && error.retryable === false))
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
function identityKey(identity: ToolGatewayIdentity): string {
|
|
363
|
+
return `${identity.serverId}\u0000${identity.toolName}`;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** A 403 like the API's for a body field Site code may not send, or null. */
|
|
367
|
+
function siteFrameBodyFieldRefusal(
|
|
368
|
+
path: string,
|
|
369
|
+
message: SiteHttpRequest,
|
|
370
|
+
input: { workspaceId: string; artifactId?: string | undefined },
|
|
371
|
+
): Response | null {
|
|
372
|
+
if (typeof message.body !== "string" || message.method === "GET") return null;
|
|
373
|
+
const operation = siteFrameOperation(
|
|
374
|
+
message.method,
|
|
375
|
+
path.split("?")[0]!,
|
|
376
|
+
input.workspaceId,
|
|
377
|
+
input.artifactId ?? null,
|
|
378
|
+
);
|
|
379
|
+
if (!operation) return null;
|
|
380
|
+
let body: unknown;
|
|
381
|
+
try {
|
|
382
|
+
body = JSON.parse(message.body);
|
|
383
|
+
} catch {
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
const field = siteFrameBodyRefusal(operation.operation.id, operation.suffix, body);
|
|
387
|
+
if (!field) return null;
|
|
388
|
+
return Response.json(
|
|
389
|
+
{
|
|
390
|
+
error: {
|
|
391
|
+
status: 403,
|
|
392
|
+
code: "forbidden",
|
|
393
|
+
message:
|
|
394
|
+
"A Site page can send message text only. Approvals, answers, resources, credentials and connection choices happen in the console.",
|
|
395
|
+
retryable: false,
|
|
396
|
+
details: { code: "site_frame_field_forbidden", field },
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
{ status: 403 },
|
|
400
|
+
);
|
|
401
|
+
}
|