@ai-matrx/api 0.0.0-stage → 0.1.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/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +18 -2
- package/dist/account.cjs +670 -0
- package/dist/account.d.cts +3246 -0
- package/dist/account.d.ts +3246 -0
- package/dist/account.js +558 -0
- package/dist/agents.cjs +598 -0
- package/dist/agents.d.cts +3417 -0
- package/dist/agents.d.ts +3417 -0
- package/dist/agents.js +497 -0
- package/dist/ai.cjs +109 -0
- package/dist/ai.d.cts +493 -0
- package/dist/ai.d.ts +493 -0
- package/dist/ai.js +91 -0
- package/dist/apps.cjs +394 -0
- package/dist/apps.d.cts +1094 -0
- package/dist/apps.d.ts +1094 -0
- package/dist/apps.js +328 -0
- package/dist/architecture.cjs +1223 -0
- package/dist/architecture.d.cts +4701 -0
- package/dist/architecture.d.ts +4701 -0
- package/dist/architecture.js +1016 -0
- package/dist/audio.cjs +226 -0
- package/dist/audio.d.cts +723 -0
- package/dist/audio.d.ts +723 -0
- package/dist/audio.js +188 -0
- package/dist/chat.cjs +538 -0
- package/dist/chat.d.cts +2251 -0
- package/dist/chat.d.ts +2251 -0
- package/dist/chat.js +447 -0
- package/dist/chunk-2SXAAKGV.cjs +279 -0
- package/dist/chunk-AY5N6EA4.js +43 -0
- package/dist/chunk-BMQI7UET.cjs +48 -0
- package/dist/chunk-FNIYADWI.cjs +59 -0
- package/dist/chunk-KIEAFMKB.js +264 -0
- package/dist/chunk-UONBYRE4.js +1012 -0
- package/dist/chunk-YTEOFGFC.js +57 -0
- package/dist/chunk-YUDQWDCM.cjs +1047 -0
- package/dist/code.cjs +561 -0
- package/dist/code.d.cts +1976 -0
- package/dist/code.d.ts +1976 -0
- package/dist/code.js +467 -0
- package/dist/commerce.cjs +22 -0
- package/dist/commerce.d.cts +45 -0
- package/dist/commerce.d.ts +45 -0
- package/dist/commerce.js +18 -0
- package/dist/communications.cjs +215 -0
- package/dist/communications.d.cts +948 -0
- package/dist/communications.d.ts +948 -0
- package/dist/communications.js +179 -0
- package/dist/computer.cjs +330 -0
- package/dist/computer.d.cts +733 -0
- package/dist/computer.d.ts +733 -0
- package/dist/computer.js +275 -0
- package/dist/content.cjs +425 -0
- package/dist/content.d.cts +1796 -0
- package/dist/content.d.ts +1796 -0
- package/dist/content.js +354 -0
- package/dist/credentials.cjs +22 -0
- package/dist/credentials.d.cts +82 -0
- package/dist/credentials.d.ts +82 -0
- package/dist/credentials.js +1 -0
- package/dist/crm.cjs +271 -0
- package/dist/crm.d.cts +1731 -0
- package/dist/crm.d.ts +1731 -0
- package/dist/crm.js +226 -0
- package/dist/data.cjs +382 -0
- package/dist/data.d.cts +1860 -0
- package/dist/data.d.ts +1860 -0
- package/dist/data.js +318 -0
- package/dist/education.cjs +28 -0
- package/dist/education.d.cts +73 -0
- package/dist/education.d.ts +73 -0
- package/dist/education.js +23 -0
- package/dist/files.cjs +1330 -0
- package/dist/files.d.cts +4956 -0
- package/dist/files.d.ts +4956 -0
- package/dist/files.js +1104 -0
- package/dist/human-resources.cjs +178 -0
- package/dist/human-resources.d.cts +1208 -0
- package/dist/human-resources.d.ts +1208 -0
- package/dist/human-resources.js +148 -0
- package/dist/index-C2E-Cp19.d.ts +385 -0
- package/dist/index-wC2vtb70.d.cts +385 -0
- package/dist/index.cjs +1934 -0
- package/dist/index.d.cts +2006 -0
- package/dist/index.d.ts +2006 -0
- package/dist/index.js +1668 -0
- package/dist/integrations.cjs +5116 -0
- package/dist/integrations.d.cts +19213 -0
- package/dist/integrations.d.ts +19213 -0
- package/dist/integrations.js +4263 -0
- package/dist/intelligence.cjs +1305 -0
- package/dist/intelligence.d.cts +4658 -0
- package/dist/intelligence.d.ts +4658 -0
- package/dist/intelligence.js +1196 -0
- package/dist/knowledge.cjs +1113 -0
- package/dist/knowledge.d.cts +5427 -0
- package/dist/knowledge.d.ts +5427 -0
- package/dist/knowledge.js +921 -0
- package/dist/legal.cjs +225 -0
- package/dist/legal.d.cts +1038 -0
- package/dist/legal.d.ts +1038 -0
- package/dist/legal.js +187 -0
- package/dist/marketing.cjs +1155 -0
- package/dist/marketing.d.cts +7741 -0
- package/dist/marketing.d.ts +7741 -0
- package/dist/marketing.js +951 -0
- package/dist/masterwork.cjs +283 -0
- package/dist/masterwork.d.cts +1938 -0
- package/dist/masterwork.d.ts +1938 -0
- package/dist/masterwork.js +231 -0
- package/dist/media.cjs +610 -0
- package/dist/media.d.cts +2270 -0
- package/dist/media.d.ts +2270 -0
- package/dist/media.js +507 -0
- package/dist/net.cjs +67 -0
- package/dist/net.d.cts +204 -0
- package/dist/net.d.ts +204 -0
- package/dist/net.js +2 -0
- package/dist/platform.cjs +150 -0
- package/dist/platform.d.cts +572 -0
- package/dist/platform.d.ts +572 -0
- package/dist/platform.js +125 -0
- package/dist/publish.cjs +248 -0
- package/dist/publish.d.cts +1431 -0
- package/dist/publish.d.ts +1431 -0
- package/dist/publish.js +206 -0
- package/dist/schemas.cjs +2 -0
- package/dist/schemas.d.cts +1353 -0
- package/dist/schemas.d.ts +1353 -0
- package/dist/schemas.js +1 -0
- package/dist/stream.cjs +44 -0
- package/dist/stream.d.cts +2 -0
- package/dist/stream.d.ts +2 -0
- package/dist/stream.js +3 -0
- package/dist/web.cjs +1045 -0
- package/dist/web.d.cts +4186 -0
- package/dist/web.d.ts +4186 -0
- package/dist/web.js +867 -0
- package/dist/workflows.cjs +825 -0
- package/dist/workflows.d.cts +4699 -0
- package/dist/workflows.d.ts +4699 -0
- package/dist/workflows.js +686 -0
- package/package.json +418 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,1668 @@
|
|
|
1
|
+
export { monitorStream } from './chunk-YTEOFGFC.js';
|
|
2
|
+
import { OrganizationContextError, BackendApiError, parseHttpError, MatrxApiError, applyOrganizationContextHeader, extractMatrxErrorCode, extractMatrxErrorMessage, readMatrxNdjsonStream, readMatrxSseStream, requireOrganizationContext, StreamTransportError } from './chunk-UONBYRE4.js';
|
|
3
|
+
export { BackendApiError, CONTRACT_SHA256, DEPLOYMENTS, DOMAINS, MatrxApiError, OrganizationContextError, StreamTransportError, applyOrganizationContextHeader, assertOrganizationMatchesOperation, assertQueryOrganizationMatchesContext, createApi, createOrganizationOperation, describeBackendFailure, extractMatrxErrorCode, extractMatrxErrorMessage, getUserMessage, isGenericUserMessage, isStreamTransportLost, openJsonOrStream, openStream, parseCallApiError, parseHttpError, parseHttpErrorBody, parsePersistedBackendError, parseStreamError, requireOrganizationContext, unwrapUpstreamError } from './chunk-UONBYRE4.js';
|
|
4
|
+
import { resilientFetch, isNetError, extractErrorMessage } from './chunk-KIEAFMKB.js';
|
|
5
|
+
export { AbortedError, ConnectTimeoutError, HeartbeatTimeoutError, HttpError, NetError, NetworkError, OfflineError, TotalTimeoutError, extractErrorMessage, isNetError, isTransportFailure, resilientFetch, toNetError, withRetry } from './chunk-KIEAFMKB.js';
|
|
6
|
+
import { credentialToHeaders } from './chunk-AY5N6EA4.js';
|
|
7
|
+
export { API_KEY_PREFIX, MATRX_HEADERS, credentialToHeaders, toCredentialsPort } from './chunk-AY5N6EA4.js';
|
|
8
|
+
import { formatDurationSeconds } from '@ai-matrx/kit/format';
|
|
9
|
+
|
|
10
|
+
// src/core/protocol.ts
|
|
11
|
+
var MATRX_AI_API_VERSION_DEFAULT = "v2";
|
|
12
|
+
var V2_COVERED_AI_PATH_TEMPLATES = [
|
|
13
|
+
"/ai/manual",
|
|
14
|
+
"/ai/chat",
|
|
15
|
+
"/ai/agents/{agent_id}",
|
|
16
|
+
"/ai/agent/{agent_id}",
|
|
17
|
+
// singular alias — behaves identically
|
|
18
|
+
"/ai/conversations/{conversation_id}",
|
|
19
|
+
"/ai/conversation/{conversation_id}",
|
|
20
|
+
// singular alias
|
|
21
|
+
"/ai/prompts/{prompt_id}",
|
|
22
|
+
// saved-prompt execution
|
|
23
|
+
"/ai/mandates/{mandate_key}"
|
|
24
|
+
// THE MANDATE DOOR — `/v2` sibling exists (routers/v2.py)
|
|
25
|
+
];
|
|
26
|
+
function toV2Path(path) {
|
|
27
|
+
const withLead = path.startsWith("/") ? path : `/${path}`;
|
|
28
|
+
if (withLead.startsWith("/api/")) {
|
|
29
|
+
const rest = withLead.slice("/api".length);
|
|
30
|
+
return rest.startsWith("/v2/") ? withLead : `/api/v2${rest}`;
|
|
31
|
+
}
|
|
32
|
+
return withLead.startsWith("/v2/") ? withLead : `/v2${withLead}`;
|
|
33
|
+
}
|
|
34
|
+
var COVERED_INTERPOLATED_PATH = [
|
|
35
|
+
/^(?:\/api)?\/ai\/manual$/,
|
|
36
|
+
/^(?:\/api)?\/ai\/chat$/,
|
|
37
|
+
/^(?:\/api)?\/ai\/agents\/[^/]+$/,
|
|
38
|
+
/^(?:\/api)?\/ai\/agent\/[^/]+$/,
|
|
39
|
+
/^(?:\/api)?\/ai\/conversations\/[^/]+$/,
|
|
40
|
+
/^(?:\/api)?\/ai\/conversation\/[^/]+$/,
|
|
41
|
+
/^(?:\/api)?\/ai\/prompts\/[^/]+$/,
|
|
42
|
+
// `/warm` etc. deliberately don't match
|
|
43
|
+
/^(?:\/api)?\/ai\/mandates\/[^/]+$/
|
|
44
|
+
// the mandate door
|
|
45
|
+
];
|
|
46
|
+
function isCoveredAiPath(path) {
|
|
47
|
+
return COVERED_INTERPOLATED_PATH.some((re) => re.test(path));
|
|
48
|
+
}
|
|
49
|
+
function isV2Path(pathOrUrl) {
|
|
50
|
+
return /\/v2\/ai\//.test(pathOrUrl);
|
|
51
|
+
}
|
|
52
|
+
function toV1FallbackUrl(url) {
|
|
53
|
+
return url.replace(/\/v2\/ai\//, "/ai/");
|
|
54
|
+
}
|
|
55
|
+
function applyAiApiVersion(path, version) {
|
|
56
|
+
if (version !== "v2") return path;
|
|
57
|
+
return isCoveredAiPath(path) ? toV2Path(path) : path;
|
|
58
|
+
}
|
|
59
|
+
async function fetchWithMatrxProtocolFallback(url, init, opts = {}) {
|
|
60
|
+
const { onDowngrade, ...fetchOpts } = opts;
|
|
61
|
+
const logDowngrade = (reason, status2) => {
|
|
62
|
+
console.warn(
|
|
63
|
+
`[matrx] ai_v2_downgrade \u2192 retrying on v1. v2=${url} reason=${reason}`
|
|
64
|
+
);
|
|
65
|
+
onDowngrade?.({ url, reason, ...status2 !== void 0 ? { status: status2 } : {} });
|
|
66
|
+
};
|
|
67
|
+
if (!isV2Path(url)) return resilientFetch(url, init, fetchOpts);
|
|
68
|
+
let result;
|
|
69
|
+
try {
|
|
70
|
+
result = await resilientFetch(url, init, fetchOpts);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
const isAbort2 = isNetError(err) && err.code === "aborted" || err instanceof Error && err.name === "AbortError";
|
|
73
|
+
if (isAbort2) throw err;
|
|
74
|
+
logDowngrade(String(err));
|
|
75
|
+
return resilientFetch(toV1FallbackUrl(url), init, fetchOpts);
|
|
76
|
+
}
|
|
77
|
+
const status = result.response.status;
|
|
78
|
+
if (status === 404 || status === 405 || status >= 500) {
|
|
79
|
+
logDowngrade(`HTTP ${status}`, status);
|
|
80
|
+
return resilientFetch(toV1FallbackUrl(url), init, fetchOpts);
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
function aiVersionPathOverrides(version) {
|
|
85
|
+
if (version !== "v2") return {};
|
|
86
|
+
const map = {};
|
|
87
|
+
for (const template of V2_COVERED_AI_PATH_TEMPLATES) {
|
|
88
|
+
map[template] = toV2Path(template);
|
|
89
|
+
}
|
|
90
|
+
return map;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/core/call.ts
|
|
94
|
+
function buildMatrxRequestUrl(baseUrl, pathTemplate, pathParams, queryParams) {
|
|
95
|
+
let resolvedPath = pathTemplate;
|
|
96
|
+
if (pathParams) {
|
|
97
|
+
for (const [key, value] of Object.entries(pathParams)) {
|
|
98
|
+
resolvedPath = resolvedPath.replace(`{${key}}`, encodeURIComponent(value));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const fullPath = resolvedPath.startsWith("/api/") ? resolvedPath.slice(4) : resolvedPath;
|
|
102
|
+
const url = `${baseUrl}${fullPath}`;
|
|
103
|
+
if (queryParams) {
|
|
104
|
+
const search = new URLSearchParams();
|
|
105
|
+
for (const [key, value] of Object.entries(queryParams)) {
|
|
106
|
+
if (value === null || value === void 0) continue;
|
|
107
|
+
const values = Array.isArray(value) ? value : [value];
|
|
108
|
+
for (const entry of values) search.append(key, String(entry));
|
|
109
|
+
}
|
|
110
|
+
const qs = search.toString();
|
|
111
|
+
if (qs) return `${url}${url.includes("?") ? "&" : "?"}${qs}`;
|
|
112
|
+
}
|
|
113
|
+
return url;
|
|
114
|
+
}
|
|
115
|
+
var MATRX_UI_ONLY_BODY_FIELDS = /* @__PURE__ */ new Set([
|
|
116
|
+
"youtube_videos",
|
|
117
|
+
"file_urls",
|
|
118
|
+
"image_urls"
|
|
119
|
+
]);
|
|
120
|
+
function buildMatrxRequestBody(body, scope) {
|
|
121
|
+
const raw = body ?? {};
|
|
122
|
+
const base = {};
|
|
123
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
124
|
+
if (!MATRX_UI_ONLY_BODY_FIELDS.has(key)) base[key] = value;
|
|
125
|
+
}
|
|
126
|
+
if (base.organization_id === null || typeof base.organization_id === "string" && base.organization_id.trim() === "") {
|
|
127
|
+
delete base.organization_id;
|
|
128
|
+
}
|
|
129
|
+
const bodyOrganizationId = base.organization_id;
|
|
130
|
+
if (scope.organization_id !== void 0 && bodyOrganizationId !== void 0 && (typeof bodyOrganizationId !== "string" || bodyOrganizationId.trim() !== scope.organization_id)) {
|
|
131
|
+
throw new OrganizationContextError(
|
|
132
|
+
"organization_context_mismatch",
|
|
133
|
+
"Request body organization_id must match the request context organization."
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (bodyOrganizationId !== void 0 && scope.organization_id !== void 0) {
|
|
137
|
+
base.organization_id = scope.organization_id;
|
|
138
|
+
}
|
|
139
|
+
const scopeFields = {
|
|
140
|
+
...scope.organization_id !== void 0 ? { organization_id: scope.organization_id } : {}
|
|
141
|
+
};
|
|
142
|
+
if (scope.project_id !== void 0) scopeFields.project_id = scope.project_id;
|
|
143
|
+
if (scope.task_id !== void 0) scopeFields.task_id = scope.task_id;
|
|
144
|
+
return { ...scopeFields, ...base };
|
|
145
|
+
}
|
|
146
|
+
var BARE_TRANSPORT_CODE = /^\s*(?:HTTP|HTTP\s*Error|Status(?:\s*Code)?)?\s*[:\-]?\s*\d{3}\s*[.:!]?\s*$/i;
|
|
147
|
+
function isBareTransportCode(text) {
|
|
148
|
+
if (!text) return false;
|
|
149
|
+
return BARE_TRANSPORT_CODE.test(text);
|
|
150
|
+
}
|
|
151
|
+
function bareStatusSentence(status) {
|
|
152
|
+
if (status === 401 || status === 403) {
|
|
153
|
+
return "The server would not let this request through \u2014 your session may have expired, or this account may not have access here. Sign in again, and if it repeats, ask an administrator.";
|
|
154
|
+
}
|
|
155
|
+
if (status === 404) {
|
|
156
|
+
return "The server has nothing at that address. Reload the page; if it repeats, report it \u2014 a client asking for something that no longer exists is a defect, not your mistake.";
|
|
157
|
+
}
|
|
158
|
+
if (status === 429) {
|
|
159
|
+
return "The server is rate-limiting this request. Wait a few seconds and try again.";
|
|
160
|
+
}
|
|
161
|
+
if (status >= 500) {
|
|
162
|
+
return "The server failed while answering this, and sent no explanation. Try again in a moment; if it repeats, report it with the request id above.";
|
|
163
|
+
}
|
|
164
|
+
return "The server refused this request and sent no reason with it \u2014 the missing reason is itself a defect worth reporting. Reload the page and try once more; if it repeats, report it with the request id above.";
|
|
165
|
+
}
|
|
166
|
+
function honestTransportMessage(raw, status) {
|
|
167
|
+
if (raw && !isBareTransportCode(raw)) return raw;
|
|
168
|
+
return bareStatusSentence(status ?? 0);
|
|
169
|
+
}
|
|
170
|
+
var SENSITIVE_HEADER_NAME = /authorization|cookie|token|api[-_]?key/i;
|
|
171
|
+
function buildSafeRequestLog(headers, body) {
|
|
172
|
+
const safeHeaders = Object.fromEntries(
|
|
173
|
+
Object.entries(headers).map(([name, value]) => [
|
|
174
|
+
name,
|
|
175
|
+
SENSITIVE_HEADER_NAME.test(name) ? "[REDACTED]" : value
|
|
176
|
+
])
|
|
177
|
+
);
|
|
178
|
+
const bodyMetadata = Array.isArray(body) ? { type: "array", itemCount: body.length } : body && typeof body === "object" ? { type: "object", keys: Object.keys(body) } : { type: body === null ? "null" : typeof body };
|
|
179
|
+
return { headers: safeHeaders, body: bodyMetadata };
|
|
180
|
+
}
|
|
181
|
+
function redactUrlForRequestLog(url) {
|
|
182
|
+
try {
|
|
183
|
+
const parsed = new URL(url);
|
|
184
|
+
for (const key of parsed.searchParams.keys()) {
|
|
185
|
+
parsed.searchParams.set(key, "[REDACTED]");
|
|
186
|
+
}
|
|
187
|
+
return parsed.toString();
|
|
188
|
+
} catch {
|
|
189
|
+
const queryIndex = url.indexOf("?");
|
|
190
|
+
return queryIndex === -1 ? url : `${url.slice(0, queryIndex)}?[REDACTED]`;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function shouldReportMatrxCallError(status, expectedErrorStatuses) {
|
|
194
|
+
return status == null || !expectedErrorStatuses?.includes(status);
|
|
195
|
+
}
|
|
196
|
+
function parseMatrxNdjsonResponse(response, signal, hooks = {}) {
|
|
197
|
+
const ids = {
|
|
198
|
+
requestId: response.headers.get("X-Request-ID"),
|
|
199
|
+
conversationId: response.headers.get("X-Conversation-ID")
|
|
200
|
+
};
|
|
201
|
+
async function* events() {
|
|
202
|
+
if (!response.body) {
|
|
203
|
+
throw new BackendApiError({
|
|
204
|
+
code: "internal_error",
|
|
205
|
+
detail: "Response has no body",
|
|
206
|
+
userMessage: "No response received from server"
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
for await (const envelope of readMatrxNdjsonStream(response.body, {
|
|
211
|
+
...signal ? { signal } : {},
|
|
212
|
+
...hooks.onMalformedLine ? { onMalformedLine: hooks.onMalformedLine } : {},
|
|
213
|
+
...hooks.onUnknownEnvelope ? { onUnknownEnvelope: hooks.onUnknownEnvelope } : {}
|
|
214
|
+
})) {
|
|
215
|
+
hooks.onEvent?.(envelope, ids);
|
|
216
|
+
yield envelope;
|
|
217
|
+
}
|
|
218
|
+
} catch (error) {
|
|
219
|
+
if (signal?.aborted || error instanceof Error && error.name === "AbortError") {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const transportError = error instanceof BackendApiError ? error : new StreamTransportError({
|
|
223
|
+
detail: error instanceof Error ? error.message : "The response stream ended unexpectedly.",
|
|
224
|
+
details: error,
|
|
225
|
+
...ids.requestId ? { requestId: ids.requestId } : {}
|
|
226
|
+
});
|
|
227
|
+
hooks.onTransportError?.(transportError, ids);
|
|
228
|
+
throw transportError;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return { events: events(), ...ids };
|
|
232
|
+
}
|
|
233
|
+
var DEFAULT_CONNECT_TIMEOUT_MS = 15e3;
|
|
234
|
+
var DEFAULT_JSON_TOTAL_TIMEOUT_MS = 3e4;
|
|
235
|
+
async function httpErrorFrom(response) {
|
|
236
|
+
const serverDetail = await response.json().catch(() => void 0);
|
|
237
|
+
return {
|
|
238
|
+
type: response.status >= 400 && response.status < 500 ? "validation_error" : "http_error",
|
|
239
|
+
message: extractMatrxErrorMessage(serverDetail) ?? bareStatusSentence(response.status),
|
|
240
|
+
status: response.status,
|
|
241
|
+
serverDetail
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
async function executeMatrxCall(request, hooks = {}) {
|
|
245
|
+
const upper = request.method.toUpperCase();
|
|
246
|
+
const sendsBody = request.stream || upper !== "GET" && upper !== "HEAD";
|
|
247
|
+
const { response } = await fetchWithMatrxProtocolFallback(
|
|
248
|
+
request.url,
|
|
249
|
+
{
|
|
250
|
+
method: request.method,
|
|
251
|
+
headers: request.headers,
|
|
252
|
+
...sendsBody ? { body: JSON.stringify(request.body) } : {}
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
...request.signal ? { signal: request.signal } : {},
|
|
256
|
+
connectTimeoutMs: request.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS,
|
|
257
|
+
totalTimeoutMs: request.stream ? null : request.totalTimeoutMs === void 0 ? DEFAULT_JSON_TOTAL_TIMEOUT_MS : request.totalTimeoutMs,
|
|
258
|
+
throwOnHttpError: false,
|
|
259
|
+
...hooks.onProtocolDowngrade ? { onDowngrade: hooks.onProtocolDowngrade } : {}
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
const requestId = response.headers.get("X-Request-ID");
|
|
263
|
+
const conversationId = response.headers.get("X-Conversation-ID");
|
|
264
|
+
const idFields = {
|
|
265
|
+
...requestId !== null ? { requestId } : {},
|
|
266
|
+
...conversationId !== null ? { conversationId } : {}
|
|
267
|
+
};
|
|
268
|
+
if (!response.ok) {
|
|
269
|
+
const error = await httpErrorFrom(response);
|
|
270
|
+
if (request.stream) request.onStreamError?.(error);
|
|
271
|
+
return { ...idFields, error };
|
|
272
|
+
}
|
|
273
|
+
if (!request.stream) {
|
|
274
|
+
const data = response.status === 204 ? void 0 : await response.json();
|
|
275
|
+
return { data, ...idFields };
|
|
276
|
+
}
|
|
277
|
+
if (request.consumeStream) {
|
|
278
|
+
request.onStreamStart?.(requestId, conversationId);
|
|
279
|
+
await request.consumeStream(response, { requestId, conversationId });
|
|
280
|
+
request.onStreamComplete?.(requestId, conversationId);
|
|
281
|
+
return idFields;
|
|
282
|
+
}
|
|
283
|
+
const parse = hooks.parseStream ?? ((res, signal) => parseMatrxNdjsonResponse(res, signal));
|
|
284
|
+
const parsed = parse(response, request.signal);
|
|
285
|
+
request.onStreamStart?.(parsed.requestId, parsed.conversationId);
|
|
286
|
+
for await (const event of parsed.events) {
|
|
287
|
+
request.onStreamEvent?.(event);
|
|
288
|
+
}
|
|
289
|
+
request.onStreamComplete?.(parsed.requestId, parsed.conversationId);
|
|
290
|
+
return {
|
|
291
|
+
...parsed.requestId !== null ? { requestId: parsed.requestId } : {},
|
|
292
|
+
...parsed.conversationId !== null ? { conversationId: parsed.conversationId } : {}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
async function sendMatrxRequest(url, init = {}, options = {}) {
|
|
296
|
+
const callerSignal = options.signal ?? init.signal ?? void 0;
|
|
297
|
+
if (options.timeoutMs === void 0) {
|
|
298
|
+
return fetch(url, { ...init, ...callerSignal ? { signal: callerSignal } : {} });
|
|
299
|
+
}
|
|
300
|
+
const controller = new AbortController();
|
|
301
|
+
const onCallerAbort = () => controller.abort(callerSignal?.reason);
|
|
302
|
+
if (callerSignal) {
|
|
303
|
+
if (callerSignal.aborted) controller.abort(callerSignal.reason);
|
|
304
|
+
else callerSignal.addEventListener("abort", onCallerAbort, { once: true });
|
|
305
|
+
}
|
|
306
|
+
let timedOut = false;
|
|
307
|
+
const timer = setTimeout(() => {
|
|
308
|
+
timedOut = true;
|
|
309
|
+
controller.abort();
|
|
310
|
+
}, options.timeoutMs);
|
|
311
|
+
try {
|
|
312
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
313
|
+
} catch (error) {
|
|
314
|
+
if (timedOut) {
|
|
315
|
+
throw new BackendApiError({
|
|
316
|
+
code: "request_timeout",
|
|
317
|
+
detail: `${init.method ?? "GET"} ${url} exceeded ${options.timeoutMs}ms`,
|
|
318
|
+
userMessage: "The request timed out \u2014 please retry.",
|
|
319
|
+
status: 504
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
throw error;
|
|
323
|
+
} finally {
|
|
324
|
+
clearTimeout(timer);
|
|
325
|
+
callerSignal?.removeEventListener("abort", onCallerAbort);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
async function readMatrxJsonResponse(response) {
|
|
329
|
+
if (!response.ok) throw await parseHttpError(response);
|
|
330
|
+
if (response.status === 204) return null;
|
|
331
|
+
return await response.json();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// src/core/client.ts
|
|
335
|
+
function classifyStatus(status) {
|
|
336
|
+
return status >= 400 && status < 500 ? "validation_error" : "http_error";
|
|
337
|
+
}
|
|
338
|
+
function normalizeMatrxError(err) {
|
|
339
|
+
if (err instanceof OrganizationContextError) {
|
|
340
|
+
return {
|
|
341
|
+
type: "validation_error",
|
|
342
|
+
message: err.message,
|
|
343
|
+
code: err.code,
|
|
344
|
+
name: err.name,
|
|
345
|
+
...err.stack !== void 0 ? { stack: err.stack } : {}
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
349
|
+
return { type: "abort_error", message: "Request was cancelled." };
|
|
350
|
+
}
|
|
351
|
+
if (err instanceof MatrxApiError) {
|
|
352
|
+
return {
|
|
353
|
+
type: classifyStatus(err.status),
|
|
354
|
+
message: honestTransportMessage(err.message, err.status),
|
|
355
|
+
status: err.status,
|
|
356
|
+
serverDetail: err.serverDetail,
|
|
357
|
+
...err.code ? { code: err.code } : {},
|
|
358
|
+
name: err.name
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
if (err instanceof BackendApiError) {
|
|
362
|
+
return {
|
|
363
|
+
type: "network_error",
|
|
364
|
+
message: honestTransportMessage(
|
|
365
|
+
err.detail || err.userMessage,
|
|
366
|
+
err.status ?? void 0
|
|
367
|
+
),
|
|
368
|
+
code: err.code,
|
|
369
|
+
name: err.name,
|
|
370
|
+
...err.status !== null ? { status: err.status } : {},
|
|
371
|
+
...err.details !== null ? { serverDetail: err.details } : {}
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
if (isNetError(err)) {
|
|
375
|
+
if (err.code === "aborted") {
|
|
376
|
+
return { type: "abort_error", message: err.message };
|
|
377
|
+
}
|
|
378
|
+
if (err.code === "http") {
|
|
379
|
+
const status = err.status ?? 0;
|
|
380
|
+
return {
|
|
381
|
+
type: classifyStatus(status),
|
|
382
|
+
message: honestTransportMessage(err.message, status),
|
|
383
|
+
status
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
return { type: "network_error", message: err.message };
|
|
387
|
+
}
|
|
388
|
+
if (err instanceof Error) {
|
|
389
|
+
const httpMatch = err.message.match(/HTTP (\d+):\s*(.*)/);
|
|
390
|
+
if (httpMatch) {
|
|
391
|
+
const status = parseInt(httpMatch[1], 10);
|
|
392
|
+
return {
|
|
393
|
+
type: classifyStatus(status),
|
|
394
|
+
message: honestTransportMessage(httpMatch[2] ?? err.message, status),
|
|
395
|
+
status
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
if (isBareTransportCode(err.message)) {
|
|
399
|
+
const status = Number.parseInt(err.message.replace(/\D+/g, ""), 10);
|
|
400
|
+
return {
|
|
401
|
+
type: classifyStatus(status),
|
|
402
|
+
message: bareStatusSentence(status),
|
|
403
|
+
status
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
return { type: "network_error", message: err.message };
|
|
407
|
+
}
|
|
408
|
+
return { type: "unknown", message: extractErrorMessage(err) };
|
|
409
|
+
}
|
|
410
|
+
var PRODUCTION_CONNECT_TIMEOUT_MS = 15e3;
|
|
411
|
+
function stripContentType(headers) {
|
|
412
|
+
return Object.fromEntries(
|
|
413
|
+
Object.entries(headers).filter(
|
|
414
|
+
([name]) => name.toLowerCase() !== "content-type"
|
|
415
|
+
)
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
var ORGANIZATION_HEADER = "x-organization-id";
|
|
419
|
+
function readOrganizationHeader(headers) {
|
|
420
|
+
for (const [name, value] of Object.entries(headers ?? {})) {
|
|
421
|
+
if (name.toLowerCase() === ORGANIZATION_HEADER && value.trim().length > 0) {
|
|
422
|
+
return value;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
function stripOrganizationHeader(headers) {
|
|
428
|
+
return Object.fromEntries(
|
|
429
|
+
Object.entries(headers).filter(
|
|
430
|
+
([name]) => name.toLowerCase() !== ORGANIZATION_HEADER
|
|
431
|
+
)
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
function createMatrxTransport(options) {
|
|
435
|
+
const {
|
|
436
|
+
baseUrl,
|
|
437
|
+
resolveTarget,
|
|
438
|
+
credentials,
|
|
439
|
+
organizationId,
|
|
440
|
+
aiApiVersion,
|
|
441
|
+
connectTimeoutMs = PRODUCTION_CONNECT_TIMEOUT_MS,
|
|
442
|
+
totalTimeoutMs = null,
|
|
443
|
+
expectedErrorStatuses,
|
|
444
|
+
diagnostics,
|
|
445
|
+
source = "matrxTransport"
|
|
446
|
+
} = options;
|
|
447
|
+
if (baseUrl === void 0 === (resolveTarget === void 0)) {
|
|
448
|
+
throw new Error(
|
|
449
|
+
"createMatrxTransport: provide exactly one of `baseUrl` or `resolveTarget`."
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
const resolve = resolveTarget ?? (() => ({ baseUrl }));
|
|
453
|
+
const resolveVersion = () => {
|
|
454
|
+
if (aiApiVersion === void 0) return MATRX_AI_API_VERSION_DEFAULT;
|
|
455
|
+
return typeof aiApiVersion === "function" ? aiApiVersion() : aiApiVersion;
|
|
456
|
+
};
|
|
457
|
+
const resolveOrganizationId = (callOrganizationId) => {
|
|
458
|
+
if (organizationId === void 0) return null;
|
|
459
|
+
if (callOrganizationId !== null) return requireOrganizationContext(callOrganizationId);
|
|
460
|
+
const candidate = typeof organizationId === "function" ? organizationId() : organizationId;
|
|
461
|
+
return requireOrganizationContext(candidate);
|
|
462
|
+
};
|
|
463
|
+
return {
|
|
464
|
+
async fetch(path, init) {
|
|
465
|
+
const target = await resolve(init);
|
|
466
|
+
const callOrganizationId = readOrganizationHeader(init.headers);
|
|
467
|
+
const versionedPath = applyAiApiVersion(path, resolveVersion());
|
|
468
|
+
const url = `${target.baseUrl}${versionedPath}`;
|
|
469
|
+
const credentialHeaders = credentials ? credentialToHeaders(await credentials.get()) : {};
|
|
470
|
+
const policyHeaders = stripContentType(target.policyHeaders ?? {});
|
|
471
|
+
let headers = {
|
|
472
|
+
...init.headers,
|
|
473
|
+
...stripContentType(credentialHeaders),
|
|
474
|
+
...callOrganizationId === null ? policyHeaders : stripOrganizationHeader(policyHeaders)
|
|
475
|
+
};
|
|
476
|
+
const orgId = resolveOrganizationId(callOrganizationId);
|
|
477
|
+
if (orgId !== null) {
|
|
478
|
+
headers = applyOrganizationContextHeader(headers, orgId);
|
|
479
|
+
}
|
|
480
|
+
const info = {
|
|
481
|
+
url,
|
|
482
|
+
method: init.method,
|
|
483
|
+
path,
|
|
484
|
+
channel: target.channel ?? "default",
|
|
485
|
+
source
|
|
486
|
+
};
|
|
487
|
+
diagnostics?.onRequest?.(info);
|
|
488
|
+
let response;
|
|
489
|
+
try {
|
|
490
|
+
({ response } = await fetchWithMatrxProtocolFallback(
|
|
491
|
+
url,
|
|
492
|
+
{
|
|
493
|
+
method: init.method,
|
|
494
|
+
headers,
|
|
495
|
+
...init.body !== void 0 ? { body: init.body } : {}
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
...init.signal ? { signal: init.signal } : {},
|
|
499
|
+
connectTimeoutMs,
|
|
500
|
+
totalTimeoutMs,
|
|
501
|
+
throwOnHttpError: false,
|
|
502
|
+
...diagnostics?.onProtocolDowngrade ? { onDowngrade: diagnostics.onProtocolDowngrade } : {}
|
|
503
|
+
}
|
|
504
|
+
));
|
|
505
|
+
} catch (err) {
|
|
506
|
+
diagnostics?.onError?.(normalizeMatrxError(err), info);
|
|
507
|
+
throw err;
|
|
508
|
+
}
|
|
509
|
+
if (!response.ok && !expectedErrorStatuses?.includes(response.status)) {
|
|
510
|
+
const serverDetail = await response.clone().json().catch(() => void 0);
|
|
511
|
+
const code = extractMatrxErrorCode(serverDetail);
|
|
512
|
+
diagnostics?.onError?.(
|
|
513
|
+
{
|
|
514
|
+
type: classifyStatus(response.status),
|
|
515
|
+
message: extractMatrxErrorMessage(serverDetail) ?? `HTTP ${response.status}`,
|
|
516
|
+
status: response.status,
|
|
517
|
+
serverDetail,
|
|
518
|
+
...code ? { code } : {}
|
|
519
|
+
},
|
|
520
|
+
info
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
return response;
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// src/core/api-target-log.ts
|
|
529
|
+
var SUSPECT_HOST_FRAGMENTS = ["sandbox"];
|
|
530
|
+
function looksSuspect(url) {
|
|
531
|
+
const lower = url.toLowerCase();
|
|
532
|
+
return SUSPECT_HOST_FRAGMENTS.some((frag) => lower.includes(frag));
|
|
533
|
+
}
|
|
534
|
+
function logApiTarget(url, context) {
|
|
535
|
+
const { source, method, channel, activeServer, ...extra } = context;
|
|
536
|
+
let origin = url;
|
|
537
|
+
try {
|
|
538
|
+
origin = new URL(url).origin;
|
|
539
|
+
} catch {
|
|
540
|
+
}
|
|
541
|
+
const suspect = looksSuspect(url);
|
|
542
|
+
const tag = suspect ? "\u{1F6A8} API TARGET (SANDBOX?)" : "\u{1F4E1} API TARGET";
|
|
543
|
+
const style = suspect ? "font-weight:bold;color:#fff;background:#c0392b;padding:1px 4px;border-radius:3px;" : "font-weight:bold;color:#fff;background:#16a085;padding:1px 4px;border-radius:3px;";
|
|
544
|
+
const meta = {
|
|
545
|
+
origin,
|
|
546
|
+
fullUrl: url,
|
|
547
|
+
source
|
|
548
|
+
};
|
|
549
|
+
if (method) meta.method = method;
|
|
550
|
+
if (channel) meta.channel = channel;
|
|
551
|
+
if (activeServer) meta.activeServer = activeServer;
|
|
552
|
+
for (const [k, v] of Object.entries(extra)) {
|
|
553
|
+
if (v !== void 0) meta[k] = v;
|
|
554
|
+
}
|
|
555
|
+
const log = suspect ? console.error : console.info;
|
|
556
|
+
log(
|
|
557
|
+
`%c${tag}%c ${method ? method + " " : ""}${url}`,
|
|
558
|
+
style,
|
|
559
|
+
"color:inherit;",
|
|
560
|
+
meta
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// src/core/endpoint-path.ts
|
|
565
|
+
function normalizeLeadingSlash(value) {
|
|
566
|
+
const trimmed = value.trim();
|
|
567
|
+
if (!trimmed) return "";
|
|
568
|
+
const withLead = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
|
569
|
+
return withLead.length > 1 && withLead.endsWith("/") ? withLead.slice(0, -1) : withLead;
|
|
570
|
+
}
|
|
571
|
+
function resolveEndpointPath(canonicalPath, config) {
|
|
572
|
+
if (!config) return canonicalPath;
|
|
573
|
+
const override = config.pathOverrides?.[canonicalPath];
|
|
574
|
+
if (override && override.trim()) {
|
|
575
|
+
return normalizeLeadingSlash(override);
|
|
576
|
+
}
|
|
577
|
+
const version = config.apiVersion?.trim();
|
|
578
|
+
if (version) {
|
|
579
|
+
const prefix = normalizeLeadingSlash(version);
|
|
580
|
+
const path = canonicalPath.startsWith("/") ? canonicalPath : `/${canonicalPath}`;
|
|
581
|
+
return `${prefix}${path}`;
|
|
582
|
+
}
|
|
583
|
+
return canonicalPath;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// src/core/desktop-target.ts
|
|
587
|
+
var DESKTOP_NATIVE_CAPABILITY = "desktop-native";
|
|
588
|
+
function applyDesktopTargetToRequestBody(body, targetInstanceId) {
|
|
589
|
+
if (!targetInstanceId || !isRecord(body)) return;
|
|
590
|
+
const existingClient = isRecord(body.client) ? body.client : null;
|
|
591
|
+
const existingState = existingClient && isRecord(existingClient.state) ? existingClient.state : null;
|
|
592
|
+
const existingDesktopState = existingState && isRecord(existingState[DESKTOP_NATIVE_CAPABILITY]) ? existingState[DESKTOP_NATIVE_CAPABILITY] : null;
|
|
593
|
+
if (!existingClient || !existingState || !existingDesktopState) return;
|
|
594
|
+
body.target_instance_id ??= targetInstanceId;
|
|
595
|
+
existingDesktopState.target_instance_id ??= targetInstanceId;
|
|
596
|
+
}
|
|
597
|
+
function isRecord(value) {
|
|
598
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
599
|
+
}
|
|
600
|
+
var RUN_OUTPUT_KINDS = ["text", "image", "video", "audio"];
|
|
601
|
+
var RUN_WAIT_KNOB_FEATURE = "agents.run_wait";
|
|
602
|
+
var RUN_STREAM_LIFETIME_BACKSTOP_MS = 24 * 60 * 60 * 1e3;
|
|
603
|
+
function isJobOutputKind(kind) {
|
|
604
|
+
return kind !== "text";
|
|
605
|
+
}
|
|
606
|
+
function runOutputKindFromModalities(output) {
|
|
607
|
+
const set = new Set((output ?? []).map((m) => m.toLowerCase()));
|
|
608
|
+
if (set.has("video")) return "video";
|
|
609
|
+
if (set.has("image")) return "image";
|
|
610
|
+
if (set.has("audio")) return "audio";
|
|
611
|
+
return "text";
|
|
612
|
+
}
|
|
613
|
+
function runWaitKnobKey(kind) {
|
|
614
|
+
return `${kind}_seconds`;
|
|
615
|
+
}
|
|
616
|
+
async function resolveRunWaitWith(readKnob, organizationId, userId, kind) {
|
|
617
|
+
const key = runWaitKnobKey(kind);
|
|
618
|
+
if (!organizationId) {
|
|
619
|
+
return { firstResponseMs: RUN_STREAM_LIFETIME_BACKSTOP_MS, seconds: null, kind };
|
|
620
|
+
}
|
|
621
|
+
try {
|
|
622
|
+
const raw = await readKnob(organizationId, userId ?? null, {
|
|
623
|
+
feature: RUN_WAIT_KNOB_FEATURE,
|
|
624
|
+
key
|
|
625
|
+
});
|
|
626
|
+
const seconds = typeof raw === "number" ? raw : Number(raw);
|
|
627
|
+
if (!Number.isFinite(seconds) || seconds <= 0) {
|
|
628
|
+
throw new Error(`it resolves to ${JSON.stringify(raw)}, not a number of seconds`);
|
|
629
|
+
}
|
|
630
|
+
return { firstResponseMs: Math.round(seconds * 1e3), seconds, kind };
|
|
631
|
+
} catch (error) {
|
|
632
|
+
console.error(
|
|
633
|
+
`[run-wait] The "${RUN_WAIT_KNOB_FEATURE}.${key}" setting could not be read \u2014 ${error instanceof Error ? error.message : String(error)}. This run waits for the server without a first-response limit (the 24-hour stream backstop) instead of inventing one. Remedy: seed the knob (matrx-frontend migrations/agents_run_wait_knobs.sql) or fix the knob snapshot read.`
|
|
634
|
+
);
|
|
635
|
+
return {
|
|
636
|
+
firstResponseMs: RUN_STREAM_LIFETIME_BACKSTOP_MS,
|
|
637
|
+
seconds: null,
|
|
638
|
+
kind
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
var KIND_NOUN = {
|
|
643
|
+
text: "a reply",
|
|
644
|
+
image: "an image",
|
|
645
|
+
video: "a video",
|
|
646
|
+
audio: "audio"
|
|
647
|
+
};
|
|
648
|
+
function runJobLabel(kind, modelLabel) {
|
|
649
|
+
const what = kind === "text" ? "Writing a reply" : `Generating ${KIND_NOUN[kind]}`;
|
|
650
|
+
return modelLabel ? `${what} with ${modelLabel}` : what;
|
|
651
|
+
}
|
|
652
|
+
function runWaitTimeoutMessage(kind, modelLabel, seconds) {
|
|
653
|
+
const waited = seconds != null ? (
|
|
654
|
+
// An exact limit an admin configured, so both tiers: "1 minute 30 seconds".
|
|
655
|
+
`within ${formatDurationSeconds(seconds, { style: "long", parts: 2, round: "nearest" })}`
|
|
656
|
+
) : "in time";
|
|
657
|
+
const who = modelLabel ? ` (${modelLabel})` : "";
|
|
658
|
+
return `The server did not start ${kind === "text" ? "answering" : `generating ${KIND_NOUN[kind]}`}${who} ${waited}, so this page stopped waiting. The run may still finish on the server \u2014 open the conversation to check before running it again. An organization admin can lengthen this wait in the organization's configuration settings.`;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// src/core/warm.ts
|
|
662
|
+
var WARM_PATH = "/ai/warm";
|
|
663
|
+
var DEFAULT_COALESCE_MS = 50;
|
|
664
|
+
var DEFAULT_DEDUPE_MS = 6e4;
|
|
665
|
+
var SWEEP_AT = 256;
|
|
666
|
+
var QUEUE_MAX = 64;
|
|
667
|
+
function itemId(item) {
|
|
668
|
+
return item.id ? `${item.key}:${item.id}` : item.key;
|
|
669
|
+
}
|
|
670
|
+
function createWarmup(options) {
|
|
671
|
+
const {
|
|
672
|
+
transport,
|
|
673
|
+
isSignedIn,
|
|
674
|
+
scope,
|
|
675
|
+
organizationId,
|
|
676
|
+
coalesceMs = DEFAULT_COALESCE_MS,
|
|
677
|
+
dedupeMs = DEFAULT_DEDUPE_MS,
|
|
678
|
+
now = Date.now
|
|
679
|
+
} = options;
|
|
680
|
+
const sentAt = /* @__PURE__ */ new Map();
|
|
681
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
682
|
+
let queue = [];
|
|
683
|
+
let queueReason = null;
|
|
684
|
+
let timer = null;
|
|
685
|
+
let announcedFailure = false;
|
|
686
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
687
|
+
const currentScope = () => {
|
|
688
|
+
try {
|
|
689
|
+
if (!isSignedIn()) return "";
|
|
690
|
+
if (!scope) return "ready";
|
|
691
|
+
const value = scope();
|
|
692
|
+
return typeof value === "string" ? value : "";
|
|
693
|
+
} catch {
|
|
694
|
+
return "";
|
|
695
|
+
}
|
|
696
|
+
};
|
|
697
|
+
const announce = (detail) => {
|
|
698
|
+
if (announcedFailure) return;
|
|
699
|
+
announcedFailure = true;
|
|
700
|
+
try {
|
|
701
|
+
console.debug(`[matrx] warm-up not accepted (${detail})`);
|
|
702
|
+
} catch {
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
const schedule = () => {
|
|
706
|
+
if (timer === null && queue.length > 0) timer = setTimeout(flush, coalesceMs);
|
|
707
|
+
};
|
|
708
|
+
function flush() {
|
|
709
|
+
timer = null;
|
|
710
|
+
const current = currentScope();
|
|
711
|
+
if (current === "" || queue.length === 0) return;
|
|
712
|
+
const reason = queueReason ?? "route";
|
|
713
|
+
const batch = [];
|
|
714
|
+
for (const q of queue) {
|
|
715
|
+
if (q.scope !== "" && q.scope !== current) continue;
|
|
716
|
+
const key = `${current}|${itemId(q.item)}`;
|
|
717
|
+
if (inFlight.has(key) || batch.some((b) => itemId(b) === itemId(q.item))) continue;
|
|
718
|
+
const last = sentAt.get(key);
|
|
719
|
+
if (last !== void 0 && now() - last < dedupeMs) continue;
|
|
720
|
+
batch.push(q.item);
|
|
721
|
+
}
|
|
722
|
+
queue = [];
|
|
723
|
+
queueReason = null;
|
|
724
|
+
if (batch.length === 0) return;
|
|
725
|
+
const keys = batch.map((i) => `${current}|${itemId(i)}`);
|
|
726
|
+
keys.forEach((k) => inFlight.add(k));
|
|
727
|
+
const settle = (ok) => {
|
|
728
|
+
const t = now();
|
|
729
|
+
for (const k of keys) {
|
|
730
|
+
inFlight.delete(k);
|
|
731
|
+
if (ok) sentAt.set(k, t);
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
try {
|
|
735
|
+
const org = organizationId ? organizationId() : null;
|
|
736
|
+
void transport.fetch(WARM_PATH, {
|
|
737
|
+
method: "POST",
|
|
738
|
+
headers: {
|
|
739
|
+
"Content-Type": "application/json",
|
|
740
|
+
...org ? { "X-Organization-Id": org } : {}
|
|
741
|
+
},
|
|
742
|
+
body: JSON.stringify({ reason, items: batch })
|
|
743
|
+
}).then(
|
|
744
|
+
(response) => {
|
|
745
|
+
settle(response.ok);
|
|
746
|
+
if (!response.ok) announce(`HTTP ${response.status}`);
|
|
747
|
+
},
|
|
748
|
+
(err) => {
|
|
749
|
+
settle(false);
|
|
750
|
+
announce(String(err));
|
|
751
|
+
}
|
|
752
|
+
);
|
|
753
|
+
} catch (err) {
|
|
754
|
+
settle(false);
|
|
755
|
+
announce(String(err));
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return {
|
|
759
|
+
currentScope,
|
|
760
|
+
subscribe(listener) {
|
|
761
|
+
listeners.add(listener);
|
|
762
|
+
return () => {
|
|
763
|
+
listeners.delete(listener);
|
|
764
|
+
};
|
|
765
|
+
},
|
|
766
|
+
notifyScopeChanged() {
|
|
767
|
+
try {
|
|
768
|
+
if (sentAt.size >= SWEEP_AT) {
|
|
769
|
+
const t = now();
|
|
770
|
+
for (const [k, at] of sentAt) if (t - at >= dedupeMs) sentAt.delete(k);
|
|
771
|
+
}
|
|
772
|
+
const current = currentScope();
|
|
773
|
+
queue = queue.filter((q) => q.scope === "" || q.scope === current);
|
|
774
|
+
if (queue.length === 0) queueReason = null;
|
|
775
|
+
schedule();
|
|
776
|
+
for (const l of listeners) l();
|
|
777
|
+
} catch (err) {
|
|
778
|
+
announce(String(err));
|
|
779
|
+
}
|
|
780
|
+
},
|
|
781
|
+
warm(items, reason) {
|
|
782
|
+
try {
|
|
783
|
+
if (items.length === 0) return;
|
|
784
|
+
const requestScope = currentScope();
|
|
785
|
+
for (const item of items) {
|
|
786
|
+
if (queue.length >= QUEUE_MAX) break;
|
|
787
|
+
if (queue.some((q) => q.scope === requestScope && itemId(q.item) === itemId(item))) continue;
|
|
788
|
+
queue.push({
|
|
789
|
+
item: item.id ? { key: item.key, id: item.id } : { key: item.key },
|
|
790
|
+
scope: requestScope
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
queueReason ??= reason;
|
|
794
|
+
schedule();
|
|
795
|
+
} catch (err) {
|
|
796
|
+
announce(String(err));
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
var preconnected = /* @__PURE__ */ new Set();
|
|
802
|
+
function preconnectServer(baseUrl) {
|
|
803
|
+
try {
|
|
804
|
+
if (!baseUrl || typeof document === "undefined") return;
|
|
805
|
+
const origin = new URL(baseUrl).origin;
|
|
806
|
+
if (preconnected.has(origin)) return;
|
|
807
|
+
preconnected.add(origin);
|
|
808
|
+
const link = document.createElement("link");
|
|
809
|
+
link.rel = "preconnect";
|
|
810
|
+
link.href = origin;
|
|
811
|
+
link.crossOrigin = "anonymous";
|
|
812
|
+
document.head.appendChild(link);
|
|
813
|
+
} catch {
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
function resetPreconnectedForTests() {
|
|
817
|
+
preconnected.clear();
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// src/core/internal.ts
|
|
821
|
+
function encodePathSegment(value) {
|
|
822
|
+
return encodeURIComponent(value);
|
|
823
|
+
}
|
|
824
|
+
function buildQuery(params) {
|
|
825
|
+
const search = new URLSearchParams();
|
|
826
|
+
for (const [key, value] of Object.entries(params)) {
|
|
827
|
+
if (value === void 0) continue;
|
|
828
|
+
if (Array.isArray(value)) {
|
|
829
|
+
for (const entry of value) search.append(key, entry);
|
|
830
|
+
} else {
|
|
831
|
+
search.append(key, String(value));
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
const encoded = search.toString();
|
|
835
|
+
return encoded ? `?${encoded}` : "";
|
|
836
|
+
}
|
|
837
|
+
async function readServerDetail(response) {
|
|
838
|
+
try {
|
|
839
|
+
return await response.json();
|
|
840
|
+
} catch {
|
|
841
|
+
return void 0;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
async function throwApiError(path, response) {
|
|
845
|
+
throw new MatrxApiError({
|
|
846
|
+
status: response.status,
|
|
847
|
+
path,
|
|
848
|
+
serverDetail: await readServerDetail(response)
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
async function requestJson(transport, path, options) {
|
|
852
|
+
const hasBody = options.method !== "GET" && options.body !== void 0;
|
|
853
|
+
const response = await transport.fetch(path, {
|
|
854
|
+
method: options.method,
|
|
855
|
+
headers: hasBody ? { "Content-Type": "application/json" } : {},
|
|
856
|
+
...hasBody ? { body: JSON.stringify(options.body) } : {},
|
|
857
|
+
...options.signal ? { signal: options.signal } : {}
|
|
858
|
+
});
|
|
859
|
+
if (!response.ok) return throwApiError(path, response);
|
|
860
|
+
return await response.json();
|
|
861
|
+
}
|
|
862
|
+
function toRunHandle(response, options) {
|
|
863
|
+
return {
|
|
864
|
+
requestId: response.headers.get("X-Request-ID"),
|
|
865
|
+
conversationId: response.headers.get("X-Conversation-ID"),
|
|
866
|
+
events: readMatrxNdjsonStream(response.body, {
|
|
867
|
+
...options.signal ? { signal: options.signal } : {},
|
|
868
|
+
...options.maxReadAhead !== void 0 ? { maxReadAhead: options.maxReadAhead } : {},
|
|
869
|
+
...options.onMalformedLine ? { onMalformedLine: options.onMalformedLine } : {},
|
|
870
|
+
...options.onUnknownEnvelope ? { onUnknownEnvelope: options.onUnknownEnvelope } : {},
|
|
871
|
+
...options.onValidEnvelope ? { onValidEnvelope: options.onValidEnvelope } : {}
|
|
872
|
+
}),
|
|
873
|
+
response
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
async function requestStream(transport, path, options) {
|
|
877
|
+
const hasBody = options.method !== "GET" && options.body !== void 0;
|
|
878
|
+
const response = await transport.fetch(path, {
|
|
879
|
+
method: options.method,
|
|
880
|
+
headers: {
|
|
881
|
+
...hasBody ? { "Content-Type": "application/json" } : {},
|
|
882
|
+
...options.headers
|
|
883
|
+
},
|
|
884
|
+
...hasBody ? { body: JSON.stringify(options.body) } : {},
|
|
885
|
+
...options.signal ? { signal: options.signal } : {}
|
|
886
|
+
});
|
|
887
|
+
if (!response.ok) return throwApiError(path, response);
|
|
888
|
+
if (!response.body) {
|
|
889
|
+
throw new MatrxApiError({
|
|
890
|
+
status: response.status,
|
|
891
|
+
path,
|
|
892
|
+
serverDetail: { code: "missing_response_body" },
|
|
893
|
+
message: "The streaming response carried no body."
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
return response;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// src/core/conversation.ts
|
|
900
|
+
function mintMatrxConversationId() {
|
|
901
|
+
return crypto.randomUUID();
|
|
902
|
+
}
|
|
903
|
+
function newStoredConversationStart(conversationId) {
|
|
904
|
+
return {
|
|
905
|
+
conversation_id: conversationId ?? mintMatrxConversationId(),
|
|
906
|
+
is_new: true,
|
|
907
|
+
store: true
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
function continueStoredConversationStart(conversationId) {
|
|
911
|
+
return { conversation_id: conversationId, is_new: false, store: true };
|
|
912
|
+
}
|
|
913
|
+
function newEphemeralConversationStart(conversationId) {
|
|
914
|
+
return {
|
|
915
|
+
conversation_id: conversationId ?? mintMatrxConversationId(),
|
|
916
|
+
is_new: true,
|
|
917
|
+
store: false
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
function continueEphemeralConversationStart(conversationId, priorMessages) {
|
|
921
|
+
return {
|
|
922
|
+
conversation_id: conversationId,
|
|
923
|
+
is_new: false,
|
|
924
|
+
store: false,
|
|
925
|
+
prior_messages: priorMessages
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// src/core/operations.ts
|
|
930
|
+
var TERMINAL_MATRX_RUNTIME_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
931
|
+
var RUNTIME_STATUSES = /* @__PURE__ */ new Set([
|
|
932
|
+
"pending",
|
|
933
|
+
"running",
|
|
934
|
+
"paused",
|
|
935
|
+
"waiting_input",
|
|
936
|
+
"completed",
|
|
937
|
+
"failed",
|
|
938
|
+
"cancelled"
|
|
939
|
+
]);
|
|
940
|
+
async function getRuntimeOperationStatus(transport, requestId, options = {}) {
|
|
941
|
+
const query = buildQuery(options.executionId ? { execution_id: options.executionId } : {});
|
|
942
|
+
try {
|
|
943
|
+
return await requestJson(
|
|
944
|
+
transport,
|
|
945
|
+
`/runtime/operations/${encodePathSegment(requestId)}${query}`,
|
|
946
|
+
{ method: "GET", ...options.signal ? { signal: options.signal } : {} }
|
|
947
|
+
);
|
|
948
|
+
} catch (error) {
|
|
949
|
+
if (error instanceof MatrxApiError && error.status === 404) return null;
|
|
950
|
+
throw error;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
async function getRuntimeOperationsByLink(transport, linkKind, linkId, options = {}) {
|
|
954
|
+
const query = buildQuery(
|
|
955
|
+
options.limit !== void 0 ? { limit: options.limit } : {}
|
|
956
|
+
);
|
|
957
|
+
try {
|
|
958
|
+
return await requestJson(
|
|
959
|
+
transport,
|
|
960
|
+
`/runtime/operations/by-link/${encodePathSegment(linkKind)}/${encodePathSegment(linkId)}${query}`,
|
|
961
|
+
{ method: "GET", ...options.signal ? { signal: options.signal } : {} }
|
|
962
|
+
);
|
|
963
|
+
} catch (error) {
|
|
964
|
+
if (error instanceof MatrxApiError && error.status === 404) return null;
|
|
965
|
+
throw error;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
function listRuntimeOperationEvents(transport, executionId, options = {}) {
|
|
969
|
+
const query = buildQuery({
|
|
970
|
+
...options.afterSeq !== void 0 ? { after_seq: options.afterSeq } : {},
|
|
971
|
+
...options.limit !== void 0 ? { limit: options.limit } : {},
|
|
972
|
+
...options.kinds !== void 0 ? { kind: options.kinds } : {}
|
|
973
|
+
});
|
|
974
|
+
return requestJson(
|
|
975
|
+
transport,
|
|
976
|
+
`/runtime/executions/${encodePathSegment(executionId)}/events${query}`,
|
|
977
|
+
{ method: "GET", ...options.signal ? { signal: options.signal } : {} }
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
function parseEndStatus(data) {
|
|
981
|
+
if (data === null) return null;
|
|
982
|
+
try {
|
|
983
|
+
const parsed = JSON.parse(data);
|
|
984
|
+
if (typeof parsed === "object" && parsed !== null && typeof parsed.status === "string") {
|
|
985
|
+
const status = parsed.status;
|
|
986
|
+
return RUNTIME_STATUSES.has(status) ? status : null;
|
|
987
|
+
}
|
|
988
|
+
} catch {
|
|
989
|
+
}
|
|
990
|
+
return null;
|
|
991
|
+
}
|
|
992
|
+
async function* followRuntimeOperationEvents(transport, executionId, options = {}) {
|
|
993
|
+
let cursor = options.lastEventSeq ?? 0;
|
|
994
|
+
const headers = { Accept: "text/event-stream" };
|
|
995
|
+
if (cursor > 0) headers["Last-Event-ID"] = String(cursor);
|
|
996
|
+
const response = await requestStream(
|
|
997
|
+
transport,
|
|
998
|
+
`/runtime/executions/${encodePathSegment(executionId)}/events/stream`,
|
|
999
|
+
{
|
|
1000
|
+
method: "GET",
|
|
1001
|
+
headers,
|
|
1002
|
+
...options.signal ? { signal: options.signal } : {}
|
|
1003
|
+
}
|
|
1004
|
+
);
|
|
1005
|
+
const frames = readMatrxSseStream(
|
|
1006
|
+
response.body,
|
|
1007
|
+
options.onIncomplete ? { onIncomplete: options.onIncomplete } : {}
|
|
1008
|
+
);
|
|
1009
|
+
for await (const frame of frames) {
|
|
1010
|
+
if (frame.event === "end") {
|
|
1011
|
+
yield { type: "end", status: parseEndStatus(frame.data), cursor };
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
if (frame.event === "execution_event" && frame.data !== null) {
|
|
1015
|
+
let event;
|
|
1016
|
+
try {
|
|
1017
|
+
event = JSON.parse(frame.data);
|
|
1018
|
+
} catch (error) {
|
|
1019
|
+
options.onMalformedFrame?.(frame, error);
|
|
1020
|
+
yield { type: "liveness", cursor };
|
|
1021
|
+
continue;
|
|
1022
|
+
}
|
|
1023
|
+
if (frame.seq !== null && frame.seq > cursor) cursor = frame.seq;
|
|
1024
|
+
yield { type: "event", event, seq: frame.seq, cursor };
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
yield { type: "liveness", cursor };
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
async function followRuntimeOperationToEnd(transport, executionId, options) {
|
|
1031
|
+
const stallTimeoutMs = options.stallTimeoutMs ?? 45e3;
|
|
1032
|
+
const reconnectLimit = options.reconnectLimit ?? 60;
|
|
1033
|
+
const reconnectDelayMs = options.reconnectDelayMs ?? 2e3;
|
|
1034
|
+
const outer = options.signal;
|
|
1035
|
+
let cursor = options.lastEventSeq ?? 0;
|
|
1036
|
+
let failures = 0;
|
|
1037
|
+
while (!outer?.aborted && failures < reconnectLimit) {
|
|
1038
|
+
const attempt2 = new AbortController();
|
|
1039
|
+
const onOuterAbort = () => attempt2.abort();
|
|
1040
|
+
outer?.addEventListener("abort", onOuterAbort, { once: true });
|
|
1041
|
+
let stallTimer = null;
|
|
1042
|
+
const armStall = () => {
|
|
1043
|
+
if (stallTimer !== null) clearTimeout(stallTimer);
|
|
1044
|
+
stallTimer = setTimeout(() => attempt2.abort(), stallTimeoutMs);
|
|
1045
|
+
};
|
|
1046
|
+
try {
|
|
1047
|
+
const items = followRuntimeOperationEvents(transport, executionId, {
|
|
1048
|
+
lastEventSeq: cursor,
|
|
1049
|
+
signal: attempt2.signal,
|
|
1050
|
+
...options.onMalformedFrame ? { onMalformedFrame: options.onMalformedFrame } : {}
|
|
1051
|
+
});
|
|
1052
|
+
armStall();
|
|
1053
|
+
for await (const item of items) {
|
|
1054
|
+
armStall();
|
|
1055
|
+
failures = 0;
|
|
1056
|
+
cursor = item.cursor;
|
|
1057
|
+
if (item.type === "end") {
|
|
1058
|
+
return { ended: true, status: item.status };
|
|
1059
|
+
}
|
|
1060
|
+
if (item.type === "event") {
|
|
1061
|
+
options.onEvent(item.event, item.seq);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
failures += 1;
|
|
1065
|
+
} catch (error) {
|
|
1066
|
+
if (outer?.aborted) break;
|
|
1067
|
+
failures += 1;
|
|
1068
|
+
options.onAttemptError?.(error);
|
|
1069
|
+
} finally {
|
|
1070
|
+
if (stallTimer !== null) clearTimeout(stallTimer);
|
|
1071
|
+
outer?.removeEventListener("abort", onOuterAbort);
|
|
1072
|
+
}
|
|
1073
|
+
if (!outer?.aborted && failures < reconnectLimit) {
|
|
1074
|
+
await new Promise((r) => setTimeout(r, reconnectDelayMs));
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
return { ended: false, status: null };
|
|
1078
|
+
}
|
|
1079
|
+
async function rejoinRuntimeOperation(transport, requestId, options = {}) {
|
|
1080
|
+
const response = await requestStream(
|
|
1081
|
+
transport,
|
|
1082
|
+
`/runtime/operations/${encodePathSegment(requestId)}/rejoin`,
|
|
1083
|
+
{
|
|
1084
|
+
method: "POST",
|
|
1085
|
+
// The route takes no body model; the reference client posts an empty
|
|
1086
|
+
// JSON object. Match it so proxies see an ordinary JSON POST.
|
|
1087
|
+
body: {},
|
|
1088
|
+
...options.signal ? { signal: options.signal } : {}
|
|
1089
|
+
}
|
|
1090
|
+
);
|
|
1091
|
+
return toRunHandle(response, options);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
// src/core/rejoin.ts
|
|
1095
|
+
var MATRX_RUN_IN_PROGRESS = "run_in_progress";
|
|
1096
|
+
var MATRX_RESUME_CONFLICT = "resume_conflict";
|
|
1097
|
+
var MATRX_LIVE_STREAM_UNAVAILABLE = "live_stream_unavailable";
|
|
1098
|
+
function isRecord2(value) {
|
|
1099
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1100
|
+
}
|
|
1101
|
+
function nonBlank(value) {
|
|
1102
|
+
return typeof value === "string" && value.trim() ? value : null;
|
|
1103
|
+
}
|
|
1104
|
+
function detailCandidates(body) {
|
|
1105
|
+
if (!isRecord2(body)) return [];
|
|
1106
|
+
const out = [];
|
|
1107
|
+
if (isRecord2(body.detail)) out.push(body.detail);
|
|
1108
|
+
if (isRecord2(body.details)) out.push(body.details);
|
|
1109
|
+
out.push(body);
|
|
1110
|
+
return out;
|
|
1111
|
+
}
|
|
1112
|
+
function statusOf(error) {
|
|
1113
|
+
if (!isRecord2(error) && !(error instanceof Error)) return null;
|
|
1114
|
+
const status = error.status;
|
|
1115
|
+
return typeof status === "number" ? status : null;
|
|
1116
|
+
}
|
|
1117
|
+
function bodyOf(error) {
|
|
1118
|
+
if (!isRecord2(error) && !(error instanceof Error)) return void 0;
|
|
1119
|
+
return error.serverDetail;
|
|
1120
|
+
}
|
|
1121
|
+
function readMatrxErrorCode(error) {
|
|
1122
|
+
for (const candidate of detailCandidates(bodyOf(error))) {
|
|
1123
|
+
const code = nonBlank(candidate.code);
|
|
1124
|
+
if (code) return code;
|
|
1125
|
+
}
|
|
1126
|
+
for (const candidate of detailCandidates(bodyOf(error))) {
|
|
1127
|
+
const code = nonBlank(candidate.error);
|
|
1128
|
+
if (code) return code;
|
|
1129
|
+
}
|
|
1130
|
+
return null;
|
|
1131
|
+
}
|
|
1132
|
+
var PREFIXED_ERROR_CODES = [
|
|
1133
|
+
"resume_conflict",
|
|
1134
|
+
"not_resumable",
|
|
1135
|
+
"outstanding_delegated_calls",
|
|
1136
|
+
"user_request_not_found",
|
|
1137
|
+
"run_in_flight"
|
|
1138
|
+
];
|
|
1139
|
+
function readMatrxErrorCodeFromMessage(message) {
|
|
1140
|
+
if (typeof message !== "string") return null;
|
|
1141
|
+
for (const code of PREFIXED_ERROR_CODES) {
|
|
1142
|
+
if (message.startsWith(`${code}:`)) return code;
|
|
1143
|
+
}
|
|
1144
|
+
return null;
|
|
1145
|
+
}
|
|
1146
|
+
async function readConversationOrganizationId(conversationId, read, onUnreadable) {
|
|
1147
|
+
try {
|
|
1148
|
+
const value = await read(conversationId);
|
|
1149
|
+
if (typeof value === "string" && value.trim()) return value;
|
|
1150
|
+
onUnreadable?.(null);
|
|
1151
|
+
} catch (error) {
|
|
1152
|
+
onUnreadable?.(error instanceof Error ? error.message : String(error));
|
|
1153
|
+
}
|
|
1154
|
+
return null;
|
|
1155
|
+
}
|
|
1156
|
+
function firstString(candidates, key) {
|
|
1157
|
+
for (const candidate of candidates) {
|
|
1158
|
+
const value = nonBlank(candidate[key]);
|
|
1159
|
+
if (value) return value.trim();
|
|
1160
|
+
}
|
|
1161
|
+
return null;
|
|
1162
|
+
}
|
|
1163
|
+
function readLiveRunRejoin(error) {
|
|
1164
|
+
if (statusOf(error) !== 409) return null;
|
|
1165
|
+
const candidates = detailCandidates(bodyOf(error));
|
|
1166
|
+
if (candidates.length === 0) return null;
|
|
1167
|
+
const code = readMatrxErrorCode(error);
|
|
1168
|
+
if (code !== MATRX_RUN_IN_PROGRESS) return null;
|
|
1169
|
+
const liveRequestId = firstString(candidates, "live_request_id");
|
|
1170
|
+
const pathFromBody = firstString(candidates, "rejoin_path");
|
|
1171
|
+
const rejoinPath = pathFromBody && pathFromBody.startsWith("/") ? pathFromBody : liveRequestId ? runtimeOperationRejoinPath(liveRequestId) : null;
|
|
1172
|
+
if (!rejoinPath) return null;
|
|
1173
|
+
const root = bodyOf(error);
|
|
1174
|
+
return {
|
|
1175
|
+
liveRequestId,
|
|
1176
|
+
rejoinPath,
|
|
1177
|
+
runId: firstString(candidates, "run_id"),
|
|
1178
|
+
code,
|
|
1179
|
+
body: isRecord2(root) ? root : {}
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
function readLiveRunRequestId(error) {
|
|
1183
|
+
return readLiveRunRejoin(error)?.liveRequestId ?? null;
|
|
1184
|
+
}
|
|
1185
|
+
function isResumeConflict(error) {
|
|
1186
|
+
return statusOf(error) === 409 && readMatrxErrorCode(error) === MATRX_RESUME_CONFLICT;
|
|
1187
|
+
}
|
|
1188
|
+
function isLiveStreamUnavailable(error) {
|
|
1189
|
+
return statusOf(error) === 409 && readMatrxErrorCode(error) === MATRX_LIVE_STREAM_UNAVAILABLE;
|
|
1190
|
+
}
|
|
1191
|
+
function runtimeOperationRejoinPath(requestId) {
|
|
1192
|
+
return `/runtime/operations/${encodePathSegment(requestId)}/rejoin`;
|
|
1193
|
+
}
|
|
1194
|
+
function streamErrorText(eventOrPayload) {
|
|
1195
|
+
if (!isRecord2(eventOrPayload)) return null;
|
|
1196
|
+
const payload = typeof eventOrPayload.event === "string" && isRecord2(eventOrPayload.data) ? eventOrPayload.data : eventOrPayload;
|
|
1197
|
+
return nonBlank(payload.user_message) ?? nonBlank(payload.message);
|
|
1198
|
+
}
|
|
1199
|
+
async function openMatrxStream(transport, path, options = {}) {
|
|
1200
|
+
const response = await requestStream(transport, path, {
|
|
1201
|
+
method: "POST",
|
|
1202
|
+
...options.body !== void 0 ? { body: options.body } : {},
|
|
1203
|
+
...options.signal ? { signal: options.signal } : {}
|
|
1204
|
+
});
|
|
1205
|
+
return toRunHandle(response, options);
|
|
1206
|
+
}
|
|
1207
|
+
function withRunOrganization(transport, organizationId) {
|
|
1208
|
+
const orgId = requireOrganizationContext(organizationId);
|
|
1209
|
+
return {
|
|
1210
|
+
fetch: (path, init) => transport.fetch(path, {
|
|
1211
|
+
...init,
|
|
1212
|
+
headers: { ...init.headers, "X-Organization-Id": orgId }
|
|
1213
|
+
})
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
async function followWorkflowRunEvents(transport, runId, options) {
|
|
1217
|
+
const headers = { Accept: "text/event-stream" };
|
|
1218
|
+
if (options.lastEventId) headers["Last-Event-ID"] = String(options.lastEventId);
|
|
1219
|
+
let response;
|
|
1220
|
+
try {
|
|
1221
|
+
response = await requestStream(
|
|
1222
|
+
transport,
|
|
1223
|
+
`/runs/${encodePathSegment(runId)}/events/stream`,
|
|
1224
|
+
{
|
|
1225
|
+
method: "GET",
|
|
1226
|
+
headers,
|
|
1227
|
+
...options.signal ? { signal: options.signal } : {}
|
|
1228
|
+
}
|
|
1229
|
+
);
|
|
1230
|
+
} catch (error) {
|
|
1231
|
+
if (options.signal?.aborted) return { ended: false };
|
|
1232
|
+
throw error;
|
|
1233
|
+
}
|
|
1234
|
+
try {
|
|
1235
|
+
for await (const frame of readMatrxSseStream(
|
|
1236
|
+
response.body
|
|
1237
|
+
)) {
|
|
1238
|
+
if (frame.event === "end") return { ended: true };
|
|
1239
|
+
if (frame.data === null) continue;
|
|
1240
|
+
let parsed;
|
|
1241
|
+
try {
|
|
1242
|
+
parsed = JSON.parse(frame.data);
|
|
1243
|
+
} catch {
|
|
1244
|
+
continue;
|
|
1245
|
+
}
|
|
1246
|
+
if (!isRecord2(parsed)) continue;
|
|
1247
|
+
options.onEvent(parsed, parsed.event === "node_stream" ? null : frame.seq);
|
|
1248
|
+
}
|
|
1249
|
+
} catch (error) {
|
|
1250
|
+
if (!options.signal?.aborted) throw error;
|
|
1251
|
+
}
|
|
1252
|
+
return { ended: false };
|
|
1253
|
+
}
|
|
1254
|
+
async function drain(handle, onEnvelope) {
|
|
1255
|
+
for await (const envelope of handle.events) onEnvelope(envelope);
|
|
1256
|
+
}
|
|
1257
|
+
async function resumeOrRejoin(transport, resumeCall, options) {
|
|
1258
|
+
const {
|
|
1259
|
+
onEnvelope,
|
|
1260
|
+
organizationId,
|
|
1261
|
+
onRejoin,
|
|
1262
|
+
onOperationEvent,
|
|
1263
|
+
onWorkflowEvent,
|
|
1264
|
+
follow,
|
|
1265
|
+
...rest
|
|
1266
|
+
} = options;
|
|
1267
|
+
const bound = organizationId ? withRunOrganization(transport, organizationId) : transport;
|
|
1268
|
+
const streamOptions = {
|
|
1269
|
+
...rest,
|
|
1270
|
+
...options.signal ? { signal: options.signal } : {}
|
|
1271
|
+
};
|
|
1272
|
+
const signalOnly = options.signal ? { signal: options.signal } : {};
|
|
1273
|
+
let rejoin;
|
|
1274
|
+
try {
|
|
1275
|
+
const handle = await resumeCall(bound, streamOptions);
|
|
1276
|
+
await drain(handle, onEnvelope);
|
|
1277
|
+
return { kind: "resumed", requestId: handle.requestId };
|
|
1278
|
+
} catch (error) {
|
|
1279
|
+
const target = readLiveRunRejoin(error);
|
|
1280
|
+
if (!target) {
|
|
1281
|
+
if (error instanceof MatrxApiError && isResumeConflict(error)) {
|
|
1282
|
+
return { kind: "resume_conflict", error };
|
|
1283
|
+
}
|
|
1284
|
+
throw error;
|
|
1285
|
+
}
|
|
1286
|
+
rejoin = target;
|
|
1287
|
+
}
|
|
1288
|
+
onRejoin?.(rejoin);
|
|
1289
|
+
let unavailable;
|
|
1290
|
+
try {
|
|
1291
|
+
const handle = await openMatrxStream(bound, rejoin.rejoinPath, {
|
|
1292
|
+
...streamOptions,
|
|
1293
|
+
// The rejoin routes take no body model; the reference client posts {}.
|
|
1294
|
+
body: {}
|
|
1295
|
+
});
|
|
1296
|
+
await drain(handle, onEnvelope);
|
|
1297
|
+
return { kind: "rejoined", rejoin };
|
|
1298
|
+
} catch (error) {
|
|
1299
|
+
const read = readLiveStreamUnavailable(error);
|
|
1300
|
+
if (!read) throw error;
|
|
1301
|
+
unavailable = read;
|
|
1302
|
+
}
|
|
1303
|
+
return followUnavailableRejoin(bound, rejoin, unavailable, {
|
|
1304
|
+
...signalOnly,
|
|
1305
|
+
...onOperationEvent ? { onOperationEvent } : {},
|
|
1306
|
+
...onWorkflowEvent ? { onWorkflowEvent } : {},
|
|
1307
|
+
...follow ? { follow } : {}
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
function readLiveStreamUnavailable(error) {
|
|
1311
|
+
if (!isLiveStreamUnavailable(error)) return null;
|
|
1312
|
+
return { runId: firstString(detailCandidates(bodyOf(error)), "run_id") };
|
|
1313
|
+
}
|
|
1314
|
+
async function followUnavailableRejoin(transport, rejoin, unavailable, options = {}) {
|
|
1315
|
+
const bound = options.organizationId ? withRunOrganization(transport, options.organizationId) : transport;
|
|
1316
|
+
const signalOnly = options.signal ? { signal: options.signal } : {};
|
|
1317
|
+
const runId = unavailable.runId ?? rejoin.runId;
|
|
1318
|
+
if (runId && rejoin.rejoinPath.startsWith("/runtime/operations/")) {
|
|
1319
|
+
const result2 = await followWorkflowRunEvents(bound, runId, {
|
|
1320
|
+
...signalOnly,
|
|
1321
|
+
onEvent: (event, seq) => options.onWorkflowEvent?.(event, seq)
|
|
1322
|
+
});
|
|
1323
|
+
return { kind: "followed_workflow", rejoin, runId, ended: result2.ended };
|
|
1324
|
+
}
|
|
1325
|
+
const liveRequestId = rejoin.liveRequestId;
|
|
1326
|
+
const statusView = liveRequestId ? await getRuntimeOperationStatus(bound, liveRequestId, signalOnly) : null;
|
|
1327
|
+
const operation = statusView?.operations.find((op) => !op.is_terminal) ?? statusView?.operations[0] ?? null;
|
|
1328
|
+
if (!operation) {
|
|
1329
|
+
return { kind: "followed", rejoin, executionId: null, ended: false, status: null };
|
|
1330
|
+
}
|
|
1331
|
+
if (operation.is_terminal) {
|
|
1332
|
+
return {
|
|
1333
|
+
kind: "followed",
|
|
1334
|
+
rejoin,
|
|
1335
|
+
executionId: operation.execution_id,
|
|
1336
|
+
ended: true,
|
|
1337
|
+
status: operation.status
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
const result = await followRuntimeOperationToEnd(bound, operation.execution_id, {
|
|
1341
|
+
...options.follow,
|
|
1342
|
+
lastEventSeq: operation.last_event_seq,
|
|
1343
|
+
...signalOnly,
|
|
1344
|
+
onEvent: (event) => options.onOperationEvent?.(event)
|
|
1345
|
+
});
|
|
1346
|
+
return {
|
|
1347
|
+
kind: "followed",
|
|
1348
|
+
rejoin,
|
|
1349
|
+
executionId: operation.execution_id,
|
|
1350
|
+
ended: result.ended,
|
|
1351
|
+
status: result.status
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
async function attempt(run) {
|
|
1355
|
+
try {
|
|
1356
|
+
await run();
|
|
1357
|
+
return { reloaded: true };
|
|
1358
|
+
} catch (error) {
|
|
1359
|
+
return { reloaded: false, error };
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
async function settleRunPickup(pickup, options) {
|
|
1363
|
+
switch (pickup.kind) {
|
|
1364
|
+
case "resumed":
|
|
1365
|
+
case "rejoined":
|
|
1366
|
+
return { state: "streamed" };
|
|
1367
|
+
case "resume_conflict":
|
|
1368
|
+
return { state: "retry" };
|
|
1369
|
+
case "followed_workflow":
|
|
1370
|
+
case "followed": {
|
|
1371
|
+
const status = pickup.kind === "followed" ? pickup.status : null;
|
|
1372
|
+
const nothingLive = pickup.kind === "followed" && pickup.executionId === null;
|
|
1373
|
+
if (pickup.ended || nothingLive) {
|
|
1374
|
+
return { state: "settled", status, ...await attempt(options.reloadSavedTurn) };
|
|
1375
|
+
}
|
|
1376
|
+
return {
|
|
1377
|
+
state: "still_running",
|
|
1378
|
+
...await attempt(options.onStillRunning ?? options.reloadSavedTurn)
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// src/core/follow-server-job.ts
|
|
1385
|
+
function readStreamCursor(frame) {
|
|
1386
|
+
if (typeof frame !== "object" || frame === null) return null;
|
|
1387
|
+
const seq = frame.stream_seq;
|
|
1388
|
+
if (typeof seq !== "number" || !Number.isSafeInteger(seq) || seq <= 0) return null;
|
|
1389
|
+
const id = frame.stream_id;
|
|
1390
|
+
return {
|
|
1391
|
+
streamId: typeof id === "string" && id.length > 0 ? id : null,
|
|
1392
|
+
streamSeq: seq
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
var NO_SEGMENT = "";
|
|
1396
|
+
function createStreamReplayCursor(options = {}) {
|
|
1397
|
+
const marks = /* @__PURE__ */ new Map();
|
|
1398
|
+
let streamId = null;
|
|
1399
|
+
const seed = () => {
|
|
1400
|
+
marks.clear();
|
|
1401
|
+
streamId = null;
|
|
1402
|
+
const s = options.seed;
|
|
1403
|
+
if (!s) return;
|
|
1404
|
+
for (const [id, seq] of Object.entries(s.marks ?? {})) marks.set(id, seq);
|
|
1405
|
+
streamId = s.streamId ?? null;
|
|
1406
|
+
if (typeof s.lastSeq === "number" && s.lastSeq > 0) {
|
|
1407
|
+
marks.set(streamId ?? NO_SEGMENT, Math.max(s.lastSeq, marks.get(streamId ?? NO_SEGMENT) ?? 0));
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
seed();
|
|
1411
|
+
return {
|
|
1412
|
+
admit(frame) {
|
|
1413
|
+
const stamp = readStreamCursor(frame);
|
|
1414
|
+
if (!stamp) return true;
|
|
1415
|
+
const key = stamp.streamId ?? streamId ?? NO_SEGMENT;
|
|
1416
|
+
const known = marks.get(key) ?? (streamId === null ? marks.get(NO_SEGMENT) : void 0) ?? 0;
|
|
1417
|
+
if (stamp.streamSeq <= known) return false;
|
|
1418
|
+
marks.set(key, stamp.streamSeq);
|
|
1419
|
+
if (stamp.streamId !== null) {
|
|
1420
|
+
const changed = streamId !== null && stamp.streamId !== streamId;
|
|
1421
|
+
streamId = stamp.streamId;
|
|
1422
|
+
if (changed) options.onSegmentChange?.(stamp.streamId);
|
|
1423
|
+
}
|
|
1424
|
+
return true;
|
|
1425
|
+
},
|
|
1426
|
+
get streamId() {
|
|
1427
|
+
return streamId;
|
|
1428
|
+
},
|
|
1429
|
+
get lastSeq() {
|
|
1430
|
+
return marks.get(streamId ?? NO_SEGMENT) ?? 0;
|
|
1431
|
+
},
|
|
1432
|
+
get marks() {
|
|
1433
|
+
return Object.fromEntries(marks);
|
|
1434
|
+
},
|
|
1435
|
+
reset() {
|
|
1436
|
+
marks.clear();
|
|
1437
|
+
streamId = null;
|
|
1438
|
+
}
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
function isServerJobSettled(status) {
|
|
1442
|
+
return status === "none" || TERMINAL_MATRX_RUNTIME_STATUSES.has(status);
|
|
1443
|
+
}
|
|
1444
|
+
var EVENT_STATUS = {
|
|
1445
|
+
created: "pending",
|
|
1446
|
+
started: "running",
|
|
1447
|
+
resumed: "running",
|
|
1448
|
+
paused: "paused",
|
|
1449
|
+
waiting_input: "waiting_input",
|
|
1450
|
+
completed: "completed",
|
|
1451
|
+
failed: "failed",
|
|
1452
|
+
cancelled: "cancelled"
|
|
1453
|
+
};
|
|
1454
|
+
function pickServerJobOperation(operations) {
|
|
1455
|
+
if (!operations || operations.length === 0) return null;
|
|
1456
|
+
return operations.find((op) => !op.is_terminal) ?? operations[0] ?? null;
|
|
1457
|
+
}
|
|
1458
|
+
function serverJobRejoinHandle(operation) {
|
|
1459
|
+
if (!operation) return null;
|
|
1460
|
+
return operation.root_request_id || operation.request_id || null;
|
|
1461
|
+
}
|
|
1462
|
+
function isAbort(error, signal) {
|
|
1463
|
+
if (signal?.aborted) return true;
|
|
1464
|
+
return error instanceof Error && error.name === "AbortError";
|
|
1465
|
+
}
|
|
1466
|
+
async function followServerJob(transport, options) {
|
|
1467
|
+
const { target, signal } = options;
|
|
1468
|
+
const bound = options.organizationId ? withRunOrganization(transport, options.organizationId) : transport;
|
|
1469
|
+
const cursor = options.cursor ?? createStreamReplayCursor();
|
|
1470
|
+
const signalOnly = signal ? { signal } : {};
|
|
1471
|
+
let operation = null;
|
|
1472
|
+
let status = "connecting";
|
|
1473
|
+
let path = "none";
|
|
1474
|
+
let framesDelivered = 0;
|
|
1475
|
+
let framesSkipped = 0;
|
|
1476
|
+
let standDown = false;
|
|
1477
|
+
const setStatus = (next) => {
|
|
1478
|
+
if (next === status) return;
|
|
1479
|
+
status = next;
|
|
1480
|
+
options.onStatus?.(next, operation);
|
|
1481
|
+
};
|
|
1482
|
+
const currentStatus = () => status;
|
|
1483
|
+
options.onStatus?.("connecting", null);
|
|
1484
|
+
const readByRequestId = async (requestId, executionId) => {
|
|
1485
|
+
const operations = (await getRuntimeOperationStatus(bound, requestId, {
|
|
1486
|
+
...signalOnly,
|
|
1487
|
+
...executionId ? { executionId } : {}
|
|
1488
|
+
}))?.operations;
|
|
1489
|
+
return (executionId ? operations?.find((op) => op.execution_id === executionId) : void 0) ?? (executionId ? null : pickServerJobOperation(operations));
|
|
1490
|
+
};
|
|
1491
|
+
const readByLink = async (linkKind, linkId) => (await getRuntimeOperationsByLink(bound, linkKind, linkId, {
|
|
1492
|
+
limit: options.linkLimit ?? 5,
|
|
1493
|
+
...signalOnly
|
|
1494
|
+
}))?.operations;
|
|
1495
|
+
const identifyOnce = async () => {
|
|
1496
|
+
if ("requestId" in target) return readByRequestId(target.requestId);
|
|
1497
|
+
if (options.identifyLink) {
|
|
1498
|
+
return pickServerJobOperation(
|
|
1499
|
+
await options.identifyLink(target.linkKind, target.linkId, signal)
|
|
1500
|
+
);
|
|
1501
|
+
}
|
|
1502
|
+
const view = await getRuntimeOperationsByLink(bound, target.linkKind, target.linkId, {
|
|
1503
|
+
limit: options.linkLimit ?? 5,
|
|
1504
|
+
...signalOnly
|
|
1505
|
+
});
|
|
1506
|
+
return pickServerJobOperation(view?.operations);
|
|
1507
|
+
};
|
|
1508
|
+
const identify = () => options.retry ? options.retry(identifyOnce) : identifyOnce();
|
|
1509
|
+
const refresh = async () => {
|
|
1510
|
+
try {
|
|
1511
|
+
let next;
|
|
1512
|
+
const handle = serverJobRejoinHandle(operation);
|
|
1513
|
+
if (!operation || !handle) {
|
|
1514
|
+
next = await identifyOnce();
|
|
1515
|
+
} else {
|
|
1516
|
+
next = await readByRequestId(handle, operation.execution_id);
|
|
1517
|
+
if (!next) {
|
|
1518
|
+
const link = "linkKind" in target ? { kind: target.linkKind, id: target.linkId } : operation.link_kind && operation.link_id ? { kind: operation.link_kind, id: operation.link_id } : null;
|
|
1519
|
+
if (link) {
|
|
1520
|
+
const ops = await readByLink(link.kind, link.id);
|
|
1521
|
+
next = ops?.find((op) => op.execution_id === operation?.execution_id);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
if (next) {
|
|
1526
|
+
operation = next;
|
|
1527
|
+
setStatus(next.status);
|
|
1528
|
+
}
|
|
1529
|
+
} catch {
|
|
1530
|
+
}
|
|
1531
|
+
};
|
|
1532
|
+
const finish = async () => {
|
|
1533
|
+
const now = currentStatus();
|
|
1534
|
+
const final = now === "connecting" ? "none" : now;
|
|
1535
|
+
const aborted = Boolean(signal?.aborted) || standDown;
|
|
1536
|
+
const settled = !aborted && isServerJobSettled(final);
|
|
1537
|
+
let reloaded = false;
|
|
1538
|
+
let reloadError;
|
|
1539
|
+
if (settled) {
|
|
1540
|
+
try {
|
|
1541
|
+
await options.reloadSavedResult({ status: final, operation });
|
|
1542
|
+
reloaded = true;
|
|
1543
|
+
} catch (error) {
|
|
1544
|
+
reloadError = error;
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
return {
|
|
1548
|
+
status: final,
|
|
1549
|
+
settled,
|
|
1550
|
+
aborted,
|
|
1551
|
+
path,
|
|
1552
|
+
requestId: serverJobRejoinHandle(operation) ?? ("requestId" in target ? target.requestId : null),
|
|
1553
|
+
executionId: operation?.execution_id ?? null,
|
|
1554
|
+
error: operation?.error ?? null,
|
|
1555
|
+
framesDelivered,
|
|
1556
|
+
framesSkipped,
|
|
1557
|
+
reloaded,
|
|
1558
|
+
...reloadError !== void 0 ? { reloadError } : {}
|
|
1559
|
+
};
|
|
1560
|
+
};
|
|
1561
|
+
operation = await identify();
|
|
1562
|
+
if (!operation) {
|
|
1563
|
+
setStatus("none");
|
|
1564
|
+
return finish();
|
|
1565
|
+
}
|
|
1566
|
+
setStatus(operation.status);
|
|
1567
|
+
if (operation.is_terminal) {
|
|
1568
|
+
path = "already_settled";
|
|
1569
|
+
return finish();
|
|
1570
|
+
}
|
|
1571
|
+
let rejoined = false;
|
|
1572
|
+
const rejoinId = serverJobRejoinHandle(operation);
|
|
1573
|
+
if (rejoinId && !operation.waiting_input) {
|
|
1574
|
+
try {
|
|
1575
|
+
if (options.rejoinConsumer) {
|
|
1576
|
+
path = "rejoin";
|
|
1577
|
+
rejoined = true;
|
|
1578
|
+
if (currentStatus() === "pending") setStatus("running");
|
|
1579
|
+
const verdict = await options.rejoinConsumer({ operation, requestId: rejoinId });
|
|
1580
|
+
if (verdict === "stand-down") {
|
|
1581
|
+
standDown = true;
|
|
1582
|
+
return finish();
|
|
1583
|
+
}
|
|
1584
|
+
} else {
|
|
1585
|
+
const handle = await rejoinRuntimeOperation(bound, rejoinId, signalOnly);
|
|
1586
|
+
path = "rejoin";
|
|
1587
|
+
rejoined = true;
|
|
1588
|
+
if (currentStatus() === "pending") setStatus("running");
|
|
1589
|
+
for await (const envelope of handle.events) {
|
|
1590
|
+
if (!cursor.admit(envelope)) {
|
|
1591
|
+
framesSkipped += 1;
|
|
1592
|
+
continue;
|
|
1593
|
+
}
|
|
1594
|
+
framesDelivered += 1;
|
|
1595
|
+
options.onFrame?.(envelope);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
} catch (error) {
|
|
1599
|
+
if (isAbort(error, signal)) return finish();
|
|
1600
|
+
options.onRejoinUnavailable?.(error);
|
|
1601
|
+
rejoined = false;
|
|
1602
|
+
}
|
|
1603
|
+
if (signal?.aborted) return finish();
|
|
1604
|
+
await refresh();
|
|
1605
|
+
if (isServerJobSettled(currentStatus())) return finish();
|
|
1606
|
+
if (rejoined && operation && options.afterRejoin?.(operation) === "handoff") {
|
|
1607
|
+
return finish();
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
if (signal?.aborted) return finish();
|
|
1611
|
+
path = rejoined ? "rejoin" : "lifecycle";
|
|
1612
|
+
const live = operation;
|
|
1613
|
+
try {
|
|
1614
|
+
const result = await followRuntimeOperationToEnd(bound, live.execution_id, {
|
|
1615
|
+
...options.follow,
|
|
1616
|
+
lastEventSeq: live.last_event_seq,
|
|
1617
|
+
...signalOnly,
|
|
1618
|
+
onEvent: (event) => {
|
|
1619
|
+
options.onOperationEvent?.(event);
|
|
1620
|
+
const next = EVENT_STATUS[event.kind];
|
|
1621
|
+
if (next) setStatus(next);
|
|
1622
|
+
}
|
|
1623
|
+
});
|
|
1624
|
+
if (signal?.aborted) return finish();
|
|
1625
|
+
if (result.ended && result.status) setStatus(result.status);
|
|
1626
|
+
} catch (error) {
|
|
1627
|
+
if (isAbort(error, signal)) return finish();
|
|
1628
|
+
}
|
|
1629
|
+
await refresh();
|
|
1630
|
+
return finish();
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
// src/core/provider-failures.ts
|
|
1634
|
+
var clip = (value, max) => value.length > max ? value.slice(0, max) : value;
|
|
1635
|
+
function providerSessionFailureBody(failure) {
|
|
1636
|
+
const message = failure.message?.trim() || failure.error_type?.trim() || (failure.status_code ? `HTTP ${failure.status_code}` : "provider session failed");
|
|
1637
|
+
const body = {
|
|
1638
|
+
provider: failure.provider,
|
|
1639
|
+
message: clip(message, 4e3)
|
|
1640
|
+
};
|
|
1641
|
+
if (failure.model?.trim()) body.model = clip(failure.model.trim(), 200);
|
|
1642
|
+
if (failure.error_type?.trim()) body.error_type = clip(failure.error_type.trim(), 200);
|
|
1643
|
+
if (typeof failure.status_code === "number" && Number.isInteger(failure.status_code) && failure.status_code >= 100 && failure.status_code <= 599) {
|
|
1644
|
+
body.status_code = failure.status_code;
|
|
1645
|
+
}
|
|
1646
|
+
return body;
|
|
1647
|
+
}
|
|
1648
|
+
async function reportProviderSessionFailure(transport, failure, options = {}) {
|
|
1649
|
+
try {
|
|
1650
|
+
return await requestJson(
|
|
1651
|
+
transport,
|
|
1652
|
+
"/broker/provider-failures",
|
|
1653
|
+
{
|
|
1654
|
+
method: "POST",
|
|
1655
|
+
body: providerSessionFailureBody(failure),
|
|
1656
|
+
...options.signal ? { signal: options.signal } : {}
|
|
1657
|
+
}
|
|
1658
|
+
);
|
|
1659
|
+
} catch (error) {
|
|
1660
|
+
console.warn(
|
|
1661
|
+
`[provider-failures] could not report a ${failure.provider} session failure`,
|
|
1662
|
+
error
|
|
1663
|
+
);
|
|
1664
|
+
return null;
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
export { MATRX_AI_API_VERSION_DEFAULT, MATRX_LIVE_STREAM_UNAVAILABLE, MATRX_RESUME_CONFLICT, MATRX_RUN_IN_PROGRESS, MATRX_UI_ONLY_BODY_FIELDS, RUN_OUTPUT_KINDS, RUN_STREAM_LIFETIME_BACKSTOP_MS, RUN_WAIT_KNOB_FEATURE, TERMINAL_MATRX_RUNTIME_STATUSES, V2_COVERED_AI_PATH_TEMPLATES, WARM_PATH, aiVersionPathOverrides, applyAiApiVersion, applyDesktopTargetToRequestBody, bareStatusSentence, buildMatrxRequestBody, buildMatrxRequestUrl, buildQuery, buildSafeRequestLog, continueEphemeralConversationStart, continueStoredConversationStart, createMatrxTransport, createStreamReplayCursor, createWarmup, encodePathSegment, executeMatrxCall, fetchWithMatrxProtocolFallback, followRuntimeOperationEvents, followRuntimeOperationToEnd, followServerJob, followUnavailableRejoin, followWorkflowRunEvents, getRuntimeOperationStatus, getRuntimeOperationsByLink, honestTransportMessage, isBareTransportCode, isCoveredAiPath, isJobOutputKind, isLiveStreamUnavailable, isResumeConflict, isServerJobSettled, isV2Path, listRuntimeOperationEvents, logApiTarget, mintMatrxConversationId, newEphemeralConversationStart, newStoredConversationStart, normalizeMatrxError, openMatrxStream, parseMatrxNdjsonResponse, pickServerJobOperation, preconnectServer, providerSessionFailureBody, readConversationOrganizationId, readLiveRunRejoin, readLiveRunRequestId, readLiveStreamUnavailable, readMatrxErrorCode, readMatrxErrorCodeFromMessage, readMatrxJsonResponse, readStreamCursor, redactUrlForRequestLog, rejoinRuntimeOperation, reportProviderSessionFailure, requestJson, requestStream, resetPreconnectedForTests, resolveEndpointPath, resolveRunWaitWith, resumeOrRejoin, runJobLabel, runOutputKindFromModalities, runWaitKnobKey, runWaitTimeoutMessage, runtimeOperationRejoinPath, sendMatrxRequest, serverJobRejoinHandle, settleRunPickup, shouldReportMatrxCallError, streamErrorText, toRunHandle, toV1FallbackUrl, toV2Path, withRunOrganization };
|