@agentsky/sky 0.2.1 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-L5LLCKZW.js → chunk-TMLNYYYY.js} +8766 -2128
- package/dist/index.js +714 -227
- package/dist/src-NNPV5GRJ.js +276 -0
- package/package.json +1 -1
- package/dist/chunk-MLKGABMK.js +0 -9
- package/dist/chunk-NLEDGUN7.js +0 -39
- package/dist/config-HRDRNTUU.js +0 -17
- package/dist/src-Y2XOFL3Z.js +0 -175
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import {
|
|
2
|
+
external_exports,
|
|
3
|
+
messagePartSchema
|
|
4
|
+
} from "./chunk-TMLNYYYY.js";
|
|
5
|
+
|
|
6
|
+
// ../../packages/chat/src/stream.ts
|
|
7
|
+
async function* parseSseStream(body) {
|
|
8
|
+
const reader = body.getReader();
|
|
9
|
+
const decoder = new TextDecoder();
|
|
10
|
+
let buffer = "";
|
|
11
|
+
try {
|
|
12
|
+
while (true) {
|
|
13
|
+
const { done, value } = await reader.read();
|
|
14
|
+
if (done) break;
|
|
15
|
+
buffer += decoder.decode(value, { stream: true });
|
|
16
|
+
let sep;
|
|
17
|
+
while ((sep = buffer.match(/\r?\n\r?\n/)) && sep.index !== void 0) {
|
|
18
|
+
const raw = buffer.slice(0, sep.index);
|
|
19
|
+
buffer = buffer.slice(sep.index + sep[0].length);
|
|
20
|
+
const frame = { data: "" };
|
|
21
|
+
const dataLines = [];
|
|
22
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
23
|
+
if (!line || line.startsWith(":")) continue;
|
|
24
|
+
const colon = line.indexOf(":");
|
|
25
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
26
|
+
const value2 = colon === -1 ? "" : line.slice(colon + 1).replace(/^ /, "");
|
|
27
|
+
if (field === "id") frame.id = value2;
|
|
28
|
+
else if (field === "event") frame.event = value2;
|
|
29
|
+
else if (field === "data") dataLines.push(value2);
|
|
30
|
+
}
|
|
31
|
+
frame.data = dataLines.join("\n");
|
|
32
|
+
if (frame.id !== void 0 || frame.event !== void 0 || frame.data) yield frame;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} finally {
|
|
36
|
+
reader.releaseLock();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function* streamAgentEvents(baseUrl, options = {}) {
|
|
40
|
+
const headers = {
|
|
41
|
+
accept: "text/event-stream",
|
|
42
|
+
...options.headers ?? {}
|
|
43
|
+
};
|
|
44
|
+
if (options.lastEventId) headers["last-event-id"] = options.lastEventId;
|
|
45
|
+
const watchdog = new AbortController();
|
|
46
|
+
const onOuterAbort = () => watchdog.abort();
|
|
47
|
+
options.signal?.addEventListener("abort", onOuterAbort, { once: true });
|
|
48
|
+
if (options.signal?.aborted) watchdog.abort();
|
|
49
|
+
let idleTimer;
|
|
50
|
+
const armIdle = () => {
|
|
51
|
+
if (!options.idleTimeoutMs) return;
|
|
52
|
+
clearTimeout(idleTimer);
|
|
53
|
+
idleTimer = setTimeout(() => watchdog.abort(), options.idleTimeoutMs);
|
|
54
|
+
};
|
|
55
|
+
try {
|
|
56
|
+
armIdle();
|
|
57
|
+
const res = await fetch(`${baseUrl}/stream`, { headers, signal: watchdog.signal });
|
|
58
|
+
if (!res.ok) throw new Error(`pod stream failed ${res.status}: ${await res.text()}`);
|
|
59
|
+
if (!res.body) throw new Error("pod stream returned no body");
|
|
60
|
+
options.onOpen?.();
|
|
61
|
+
for await (const frame of parseSseStream(res.body)) {
|
|
62
|
+
armIdle();
|
|
63
|
+
let payload = {};
|
|
64
|
+
try {
|
|
65
|
+
payload = frame.data ? JSON.parse(frame.data) : {};
|
|
66
|
+
} catch {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
yield {
|
|
70
|
+
id: frame.id ?? "",
|
|
71
|
+
type: frame.event ?? (typeof payload.type === "string" ? payload.type : "unknown"),
|
|
72
|
+
detail: payload.detail ?? null,
|
|
73
|
+
data: payload.data ?? payload,
|
|
74
|
+
created_at: payload.created_at
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (!(error instanceof DOMException && error.name === "AbortError")) throw error;
|
|
79
|
+
} finally {
|
|
80
|
+
clearTimeout(idleTimer);
|
|
81
|
+
options.signal?.removeEventListener("abort", onOuterAbort);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ../../packages/chat/src/index.ts
|
|
86
|
+
var internalEventSchema = external_exports.object({
|
|
87
|
+
sessionId: external_exports.string().min(1).optional(),
|
|
88
|
+
/** Deploy-skew alias for sessionId; dropped one release after the cutover. */
|
|
89
|
+
agentInstanceId: external_exports.string().min(1).optional(),
|
|
90
|
+
message: external_exports.object({
|
|
91
|
+
id: external_exports.string().optional(),
|
|
92
|
+
parts: external_exports.array(messagePartSchema).min(1)
|
|
93
|
+
}).optional(),
|
|
94
|
+
/** A pre-built lifecycle event from the web host (session.updated /
|
|
95
|
+
* session.deleted) — emitted verbatim; the host already persisted it. */
|
|
96
|
+
lifecycle: external_exports.object({ id: external_exports.string().min(1), type: external_exports.string().min(1) }).passthrough().optional()
|
|
97
|
+
});
|
|
98
|
+
function podMessageBody(messageId, turn, resourcesRevision = 0) {
|
|
99
|
+
if ("events" in turn) {
|
|
100
|
+
const envelope = {
|
|
101
|
+
events: turn.events.system ? [turn.events.message, turn.events.system] : [turn.events.message],
|
|
102
|
+
...resourcesRevision > 0 ? { resources_revision: resourcesRevision } : {}
|
|
103
|
+
};
|
|
104
|
+
return { id: messageId, ...envelope, parts: legacyPartsOf(envelope) };
|
|
105
|
+
}
|
|
106
|
+
return { id: messageId, parts: turn.parts };
|
|
107
|
+
}
|
|
108
|
+
var UNREACHABLE_CODES = /* @__PURE__ */ new Set([
|
|
109
|
+
"ECONNREFUSED",
|
|
110
|
+
"ENOTFOUND",
|
|
111
|
+
"EAI_AGAIN",
|
|
112
|
+
"EHOSTUNREACH",
|
|
113
|
+
"ENETUNREACH",
|
|
114
|
+
"UND_ERR_CONNECT_TIMEOUT"
|
|
115
|
+
]);
|
|
116
|
+
var UNREACHABLE_PREFIXES = ["CERT_", "ERR_TLS_", "UNABLE_TO_", "DEPTH_ZERO_", "SELF_SIGNED_"];
|
|
117
|
+
function errorCode(error) {
|
|
118
|
+
const cause = error?.cause ?? error;
|
|
119
|
+
const code = cause?.code;
|
|
120
|
+
if (typeof code === "string") return code;
|
|
121
|
+
const nested = cause?.errors?.find(
|
|
122
|
+
(e) => typeof e?.code === "string"
|
|
123
|
+
);
|
|
124
|
+
return nested ? nested.code : void 0;
|
|
125
|
+
}
|
|
126
|
+
function classifyPodSendError(error) {
|
|
127
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
128
|
+
const name = error?.name;
|
|
129
|
+
if (name === "TimeoutError" || name === "AbortError") return { kind: "uncertain", reason: "timeout", detail };
|
|
130
|
+
const code = errorCode(error);
|
|
131
|
+
if (code && (UNREACHABLE_CODES.has(code) || UNREACHABLE_PREFIXES.some((prefix) => code.startsWith(prefix)))) {
|
|
132
|
+
return { kind: "undelivered", reason: "unreachable", detail: `${detail} (${code})` };
|
|
133
|
+
}
|
|
134
|
+
return { kind: "uncertain", reason: "reset", detail: code ? `${detail} (${code})` : detail };
|
|
135
|
+
}
|
|
136
|
+
function classifyPodSendStatus(status, detail) {
|
|
137
|
+
if (status >= 200 && status < 300) return { kind: "accepted", status };
|
|
138
|
+
if (status === 403) return { kind: "undelivered", reason: "forbidden", status, detail };
|
|
139
|
+
if (status === 409) return { kind: "undelivered", reason: "parked", status, detail };
|
|
140
|
+
if (status === 502 || status === 503) return { kind: "undelivered", reason: "not_serving", status, detail };
|
|
141
|
+
if (status === 504) return { kind: "uncertain", reason: "gateway_timeout", status, detail };
|
|
142
|
+
if (status >= 500) return { kind: "uncertain", reason: "server_error", status, detail };
|
|
143
|
+
return { kind: "rejected", status, detail };
|
|
144
|
+
}
|
|
145
|
+
async function sendPodMessage(baseUrl, body, options = {}) {
|
|
146
|
+
const headers = {
|
|
147
|
+
"content-type": "application/json",
|
|
148
|
+
...options.headers ?? {}
|
|
149
|
+
};
|
|
150
|
+
if (options.idempotencyKey) headers["idempotency-key"] = options.idempotencyKey;
|
|
151
|
+
let res;
|
|
152
|
+
try {
|
|
153
|
+
res = await fetch(`${baseUrl}/messages`, {
|
|
154
|
+
method: "POST",
|
|
155
|
+
headers,
|
|
156
|
+
body: JSON.stringify(body),
|
|
157
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? 2e4)
|
|
158
|
+
});
|
|
159
|
+
} catch (error) {
|
|
160
|
+
return classifyPodSendError(error);
|
|
161
|
+
}
|
|
162
|
+
const detail = res.ok ? "" : await res.text().catch(() => "");
|
|
163
|
+
return classifyPodSendStatus(res.status, detail);
|
|
164
|
+
}
|
|
165
|
+
async function getPodHealth(baseUrl, options = {}) {
|
|
166
|
+
try {
|
|
167
|
+
const res = await fetch(`${baseUrl}/health`, {
|
|
168
|
+
headers: options.headers ?? {},
|
|
169
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? 5e3)
|
|
170
|
+
});
|
|
171
|
+
const body = await res.json().catch(() => ({}));
|
|
172
|
+
const open = body.open_message_id ?? body.open_turn_id;
|
|
173
|
+
return {
|
|
174
|
+
ok: res.ok,
|
|
175
|
+
status: res.status,
|
|
176
|
+
state: typeof body.state === "string" ? body.state : void 0,
|
|
177
|
+
openMessageId: typeof open === "string" && open ? open : null
|
|
178
|
+
};
|
|
179
|
+
} catch {
|
|
180
|
+
return { ok: false, openMessageId: null };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function legacyPartsOf(body) {
|
|
184
|
+
const parts = [];
|
|
185
|
+
const user = body.events[0];
|
|
186
|
+
if (user.type !== "user.message") return parts;
|
|
187
|
+
for (const block of user.content) {
|
|
188
|
+
const index = parts.length;
|
|
189
|
+
if (block.type === "text") {
|
|
190
|
+
parts.push({ type: "text", index, text: block.text });
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (block.source.type !== "file") continue;
|
|
194
|
+
const uri = `blob://assets/${block.source.file_id.replace(/^file_/, "")}`;
|
|
195
|
+
if (block.type === "image") parts.push({ type: "image", index, media_type: "image/*", uri });
|
|
196
|
+
else parts.push({ type: "file", index, name: block.title ?? "document", media_type: "application/octet-stream", uri });
|
|
197
|
+
}
|
|
198
|
+
return parts;
|
|
199
|
+
}
|
|
200
|
+
function textFromParts(parts) {
|
|
201
|
+
const texts = [];
|
|
202
|
+
let previous;
|
|
203
|
+
for (const part of parts) {
|
|
204
|
+
const stream = part.type === "text" && part.stream_id && typeof part.stream_index === "number" && Number.isInteger(part.stream_index) && part.stream_index >= 0 ? { id: part.stream_id, index: part.stream_index } : void 0;
|
|
205
|
+
if (part.type === "text") {
|
|
206
|
+
if (stream && previous && stream.id === previous.id && stream.index === previous.index + 1) {
|
|
207
|
+
texts[texts.length - 1] += part.text;
|
|
208
|
+
} else {
|
|
209
|
+
texts.push(part.text);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
previous = stream;
|
|
213
|
+
}
|
|
214
|
+
return texts.filter(Boolean).join(" ").trim();
|
|
215
|
+
}
|
|
216
|
+
function normalizeBaseUrl(baseUrl) {
|
|
217
|
+
return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
218
|
+
}
|
|
219
|
+
async function postSessionEvents(args) {
|
|
220
|
+
const url = new URL(
|
|
221
|
+
`/api/v1/sessions/${encodeURIComponent(args.sessionId)}/events`,
|
|
222
|
+
normalizeBaseUrl(args.baseUrl)
|
|
223
|
+
);
|
|
224
|
+
const body = JSON.stringify({ events: args.events });
|
|
225
|
+
const send = async (token) => fetch(url, {
|
|
226
|
+
method: "POST",
|
|
227
|
+
headers: {
|
|
228
|
+
authorization: `Bearer ${token}`,
|
|
229
|
+
"content-type": "application/json",
|
|
230
|
+
...args.idempotencyKey ? { "idempotency-key": args.idempotencyKey } : {}
|
|
231
|
+
},
|
|
232
|
+
body,
|
|
233
|
+
signal: AbortSignal.timeout(args.timeoutMs ?? 95e3)
|
|
234
|
+
});
|
|
235
|
+
let response = await send(await args.credentials.token());
|
|
236
|
+
if (response.status === 401) response = await send(await args.credentials.renew());
|
|
237
|
+
return readResult(response);
|
|
238
|
+
}
|
|
239
|
+
async function interruptSession(args) {
|
|
240
|
+
const url = new URL(
|
|
241
|
+
`/api/v1/sessions/${encodeURIComponent(args.sessionId)}/interrupt`,
|
|
242
|
+
normalizeBaseUrl(args.baseUrl)
|
|
243
|
+
);
|
|
244
|
+
const send = async (token) => fetch(url, {
|
|
245
|
+
method: "POST",
|
|
246
|
+
headers: { authorization: `Bearer ${token}` },
|
|
247
|
+
signal: AbortSignal.timeout(args.timeoutMs ?? 1e4)
|
|
248
|
+
});
|
|
249
|
+
let response = await send(await args.credentials.token());
|
|
250
|
+
if (response.status === 401) response = await send(await args.credentials.renew());
|
|
251
|
+
return readResult(response);
|
|
252
|
+
}
|
|
253
|
+
async function readResult(response) {
|
|
254
|
+
const payload = await response.json().catch(() => ({}));
|
|
255
|
+
if (response.ok) return { ok: true, status: response.status, data: payload.data ?? [] };
|
|
256
|
+
return {
|
|
257
|
+
ok: false,
|
|
258
|
+
status: response.status,
|
|
259
|
+
code: payload.error?.code ?? `http_${response.status}`,
|
|
260
|
+
message: payload.error?.message ?? `conversation plane answered ${response.status}`
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
export {
|
|
264
|
+
classifyPodSendError,
|
|
265
|
+
classifyPodSendStatus,
|
|
266
|
+
getPodHealth,
|
|
267
|
+
internalEventSchema,
|
|
268
|
+
interruptSession,
|
|
269
|
+
legacyPartsOf,
|
|
270
|
+
parseSseStream,
|
|
271
|
+
podMessageBody,
|
|
272
|
+
postSessionEvents,
|
|
273
|
+
sendPodMessage,
|
|
274
|
+
streamAgentEvents,
|
|
275
|
+
textFromParts
|
|
276
|
+
};
|
package/package.json
CHANGED
package/dist/chunk-MLKGABMK.js
DELETED
package/dist/chunk-NLEDGUN7.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
// src/config.ts
|
|
2
|
-
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
-
import { homedir } from "os";
|
|
4
|
-
import { dirname, join } from "path";
|
|
5
|
-
var DEFAULT_API_BASE = "https://agentsky.dev";
|
|
6
|
-
function configPath() {
|
|
7
|
-
return join(process.env.SKY_CONFIG_DIR ?? join(homedir(), ".config", "sky"), "config.json");
|
|
8
|
-
}
|
|
9
|
-
function loadConfig() {
|
|
10
|
-
try {
|
|
11
|
-
return JSON.parse(readFileSync(configPath(), "utf8"));
|
|
12
|
-
} catch {
|
|
13
|
-
return {};
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
function saveConfig(config) {
|
|
17
|
-
const path = configPath();
|
|
18
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
19
|
-
writeFileSync(path, `${JSON.stringify(config, null, 2)}
|
|
20
|
-
`, { mode: 384 });
|
|
21
|
-
}
|
|
22
|
-
function apiBase() {
|
|
23
|
-
return (process.env.SKY_API_URL ?? loadConfig().apiBase ?? DEFAULT_API_BASE).replace(/\/$/, "");
|
|
24
|
-
}
|
|
25
|
-
function apiToken() {
|
|
26
|
-
return process.env.SKY_API_TOKEN ?? loadConfig().token;
|
|
27
|
-
}
|
|
28
|
-
function currentUniverse() {
|
|
29
|
-
return process.env.SKY_UNIVERSE ?? loadConfig().currentUniverse;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export {
|
|
33
|
-
configPath,
|
|
34
|
-
loadConfig,
|
|
35
|
-
saveConfig,
|
|
36
|
-
apiBase,
|
|
37
|
-
apiToken,
|
|
38
|
-
currentUniverse
|
|
39
|
-
};
|
package/dist/config-HRDRNTUU.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
apiBase,
|
|
3
|
-
apiToken,
|
|
4
|
-
configPath,
|
|
5
|
-
currentUniverse,
|
|
6
|
-
loadConfig,
|
|
7
|
-
saveConfig
|
|
8
|
-
} from "./chunk-NLEDGUN7.js";
|
|
9
|
-
import "./chunk-MLKGABMK.js";
|
|
10
|
-
export {
|
|
11
|
-
apiBase,
|
|
12
|
-
apiToken,
|
|
13
|
-
configPath,
|
|
14
|
-
currentUniverse,
|
|
15
|
-
loadConfig,
|
|
16
|
-
saveConfig
|
|
17
|
-
};
|
package/dist/src-Y2XOFL3Z.js
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
external_exports,
|
|
3
|
-
messagePartSchema
|
|
4
|
-
} from "./chunk-L5LLCKZW.js";
|
|
5
|
-
import "./chunk-MLKGABMK.js";
|
|
6
|
-
|
|
7
|
-
// ../../packages/chat/src/stream.ts
|
|
8
|
-
async function* parseSseStream(body) {
|
|
9
|
-
const reader = body.getReader();
|
|
10
|
-
const decoder = new TextDecoder();
|
|
11
|
-
let buffer = "";
|
|
12
|
-
try {
|
|
13
|
-
while (true) {
|
|
14
|
-
const { done, value } = await reader.read();
|
|
15
|
-
if (done) break;
|
|
16
|
-
buffer += decoder.decode(value, { stream: true });
|
|
17
|
-
let sep;
|
|
18
|
-
while ((sep = buffer.match(/\r?\n\r?\n/)) && sep.index !== void 0) {
|
|
19
|
-
const raw = buffer.slice(0, sep.index);
|
|
20
|
-
buffer = buffer.slice(sep.index + sep[0].length);
|
|
21
|
-
const frame = { data: "" };
|
|
22
|
-
const dataLines = [];
|
|
23
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
24
|
-
if (!line || line.startsWith(":")) continue;
|
|
25
|
-
const colon = line.indexOf(":");
|
|
26
|
-
const field = colon === -1 ? line : line.slice(0, colon);
|
|
27
|
-
const value2 = colon === -1 ? "" : line.slice(colon + 1).replace(/^ /, "");
|
|
28
|
-
if (field === "id") frame.id = value2;
|
|
29
|
-
else if (field === "event") frame.event = value2;
|
|
30
|
-
else if (field === "data") dataLines.push(value2);
|
|
31
|
-
}
|
|
32
|
-
frame.data = dataLines.join("\n");
|
|
33
|
-
if (frame.id !== void 0 || frame.event !== void 0 || frame.data) yield frame;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
} finally {
|
|
37
|
-
reader.releaseLock();
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
async function* streamAgentEvents(baseUrl, options = {}) {
|
|
41
|
-
const headers = { accept: "text/event-stream" };
|
|
42
|
-
if (options.lastEventId) headers["last-event-id"] = options.lastEventId;
|
|
43
|
-
const res = await fetch(`${baseUrl}/stream`, { headers, signal: options.signal });
|
|
44
|
-
if (!res.ok) throw new Error(`pod stream failed ${res.status}: ${await res.text()}`);
|
|
45
|
-
if (!res.body) throw new Error("pod stream returned no body");
|
|
46
|
-
try {
|
|
47
|
-
for await (const frame of parseSseStream(res.body)) {
|
|
48
|
-
let payload = {};
|
|
49
|
-
try {
|
|
50
|
-
payload = frame.data ? JSON.parse(frame.data) : {};
|
|
51
|
-
} catch {
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
yield {
|
|
55
|
-
id: frame.id ?? "",
|
|
56
|
-
type: frame.event ?? (typeof payload.type === "string" ? payload.type : "unknown"),
|
|
57
|
-
detail: payload.detail ?? null,
|
|
58
|
-
data: payload.data ?? payload,
|
|
59
|
-
created_at: payload.created_at
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
} catch (error) {
|
|
63
|
-
if (!(error instanceof DOMException && error.name === "AbortError")) throw error;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// ../../packages/chat/src/index.ts
|
|
68
|
-
var chatPlatformSchema = external_exports.enum(["telegram", "slack", "discord", "whatsapp", "web"]);
|
|
69
|
-
var chatProxyRequestSchema = external_exports.object({
|
|
70
|
-
source: external_exports.enum(["web", "gateway", "actl", "internal"]).default("internal"),
|
|
71
|
-
mode: external_exports.enum(["accepted", "wait"]).default("accepted"),
|
|
72
|
-
userId: external_exports.string().min(1).optional(),
|
|
73
|
-
surface: external_exports.object({
|
|
74
|
-
platform: chatPlatformSchema,
|
|
75
|
-
conversationKey: external_exports.string().min(1),
|
|
76
|
-
platformIdentity: external_exports.string().optional(),
|
|
77
|
-
eventId: external_exports.string().optional()
|
|
78
|
-
}).optional(),
|
|
79
|
-
parts: external_exports.array(messagePartSchema).min(1),
|
|
80
|
-
timeoutMs: external_exports.number().int().positive().max(9e5).optional(),
|
|
81
|
-
metadata: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
|
|
82
|
-
/** The inbound message's ledger id (uiMessageId) when the caller persisted it
|
|
83
|
-
* at its own ingress: the proxy echoes it as the stream frame's messageId so
|
|
84
|
-
* consumers dedup the live echo against ledger catch-up. */
|
|
85
|
-
inboundMessageId: external_exports.string().min(1).max(160).optional()
|
|
86
|
-
});
|
|
87
|
-
async function postTurn(baseUrl, body, options = {}) {
|
|
88
|
-
const timeoutMs = options.timeoutMs ?? 2e4;
|
|
89
|
-
const retryDelayMs = options.retryDelayMs ?? 4e3;
|
|
90
|
-
const deadline = Date.now() + timeoutMs;
|
|
91
|
-
let res;
|
|
92
|
-
let lastError;
|
|
93
|
-
const headers = { "content-type": "application/json" };
|
|
94
|
-
if (options.idempotencyKey) headers["idempotency-key"] = options.idempotencyKey;
|
|
95
|
-
while (Date.now() < deadline) {
|
|
96
|
-
try {
|
|
97
|
-
res = await fetch(`${baseUrl}/messages`, {
|
|
98
|
-
method: "POST",
|
|
99
|
-
headers,
|
|
100
|
-
body: JSON.stringify(body),
|
|
101
|
-
signal: AbortSignal.timeout(Math.max(1, deadline - Date.now()))
|
|
102
|
-
});
|
|
103
|
-
lastError = void 0;
|
|
104
|
-
} catch (error) {
|
|
105
|
-
lastError = error;
|
|
106
|
-
res = void 0;
|
|
107
|
-
}
|
|
108
|
-
if (res && res.ok) break;
|
|
109
|
-
if (res && res.status !== 502 && res.status !== 503 && res.status !== 504) break;
|
|
110
|
-
const remaining = deadline - Date.now();
|
|
111
|
-
if (remaining <= 0) break;
|
|
112
|
-
await new Promise((r) => setTimeout(r, Math.min(retryDelayMs, remaining)));
|
|
113
|
-
}
|
|
114
|
-
if (!res) {
|
|
115
|
-
const suffix = lastError instanceof Error ? `: ${lastError.message}` : "";
|
|
116
|
-
throw new Error(`pod unreachable${suffix}`);
|
|
117
|
-
}
|
|
118
|
-
if (!res.ok) throw new Error(`pod message failed ${res.status}: ${await res.text()}`);
|
|
119
|
-
}
|
|
120
|
-
async function sendAgentTurn(baseUrl, parts, options = {}) {
|
|
121
|
-
await postTurn(baseUrl, { parts }, options);
|
|
122
|
-
}
|
|
123
|
-
function textFromParts(parts) {
|
|
124
|
-
return parts.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join(" ").trim();
|
|
125
|
-
}
|
|
126
|
-
async function postChatProxy(args) {
|
|
127
|
-
const url = new URL(
|
|
128
|
-
`/api/v1/sessions/${encodeURIComponent(args.agentRouteId)}/messages`,
|
|
129
|
-
normalizeBaseUrl(args.baseUrl)
|
|
130
|
-
);
|
|
131
|
-
const headers = {
|
|
132
|
-
authorization: `Bearer ${args.token}`,
|
|
133
|
-
"content-type": "application/json"
|
|
134
|
-
};
|
|
135
|
-
if (args.idempotencyKey) headers["idempotency-key"] = args.idempotencyKey;
|
|
136
|
-
const request = args.request;
|
|
137
|
-
const body = {
|
|
138
|
-
parts: request.parts,
|
|
139
|
-
mode: request.mode,
|
|
140
|
-
timeoutMs: request.timeoutMs,
|
|
141
|
-
messageId: request.inboundMessageId,
|
|
142
|
-
onBehalfOf: {
|
|
143
|
-
userId: request.userId,
|
|
144
|
-
surface: request.surface ? {
|
|
145
|
-
platform: request.surface.platform,
|
|
146
|
-
conversationKey: request.surface.conversationKey,
|
|
147
|
-
eventId: request.surface.eventId
|
|
148
|
-
} : request.source === "web" ? { platform: "web", conversationKey: "direct-chat" } : void 0
|
|
149
|
-
}
|
|
150
|
-
};
|
|
151
|
-
const response = await fetch(url, {
|
|
152
|
-
method: "POST",
|
|
153
|
-
headers,
|
|
154
|
-
body: JSON.stringify(body),
|
|
155
|
-
signal: AbortSignal.timeout(args.timeoutMs ?? args.request.timeoutMs ?? 95e3)
|
|
156
|
-
});
|
|
157
|
-
const data = await response.json().catch(() => ({}));
|
|
158
|
-
if (!response.ok) {
|
|
159
|
-
const message = typeof data.error === "string" ? data.error : data.error?.message;
|
|
160
|
-
throw new Error(message ?? `chat proxy failed ${response.status}`);
|
|
161
|
-
}
|
|
162
|
-
return data;
|
|
163
|
-
}
|
|
164
|
-
function normalizeBaseUrl(value) {
|
|
165
|
-
return value.endsWith("/") ? value : `${value}/`;
|
|
166
|
-
}
|
|
167
|
-
export {
|
|
168
|
-
chatPlatformSchema,
|
|
169
|
-
chatProxyRequestSchema,
|
|
170
|
-
parseSseStream,
|
|
171
|
-
postChatProxy,
|
|
172
|
-
sendAgentTurn,
|
|
173
|
-
streamAgentEvents,
|
|
174
|
-
textFromParts
|
|
175
|
-
};
|