pinomad 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -58
- package/dist/{chunk-8yme2wep.js → chunk-014b7ne7.js} +1 -1
- package/dist/{chunk-07769pqx.js → chunk-0tesv2sr.js} +8 -8
- package/dist/{chunk-ywmm63ks.js → chunk-3p1eysm9.js} +4 -2
- package/dist/chunk-4sepcfpy.js +2994 -0
- package/dist/{chunk-0xh6cy9v.js → chunk-5gp9svzs.js} +5 -3
- package/dist/{chunk-12kaxggg.js → chunk-76pvsg2d.js} +2 -2
- package/dist/{chunk-1nyzr0mg.js → chunk-ctzgjctt.js} +2395 -839
- package/dist/{chunk-e4ahjbmz.js → chunk-jgd38rna.js} +210 -7
- package/dist/{chunk-7qb9tq54.js → chunk-khcpv54k.js} +64 -310
- package/dist/{chunk-1t58fh0k.js → chunk-m824ds0q.js} +5 -5
- package/dist/{chunk-sz3rzk55.js → chunk-qgexy01f.js} +5 -5
- package/dist/chunk-sygeb9qb.js +430 -0
- package/dist/{chunk-b1h374jr.js → chunk-v414b5pp.js} +8 -2
- package/dist/{chunk-m4c5gvce.js → chunk-vax4zspw.js} +22 -5
- package/dist/{chunk-6ysezfy5.js → chunk-vxmj0200.js} +305 -23
- package/dist/{chunk-rtwxjb90.js → chunk-vyvyaavr.js} +1 -1
- package/dist/{chunk-r6w5aqkw.js → chunk-wb10efyq.js} +17 -0
- package/dist/{chunk-fbkd73zr.js → chunk-yhf7bj5q.js} +9 -9
- package/dist/pinomad.js +10 -10
- package/package.json +12 -8
- package/web/assets/index-1g60bhv2.js +395 -0
- package/web/assets/{index-C0rwAo0H.css → index-DoyHMPwf.css} +1 -1
- package/web/index.html +2 -2
- package/dist/chunk-sj7rnvh4.js +0 -719
- package/dist/chunk-znhn8af7.js +0 -33
- package/web/assets/index-DGee2kNU.js +0 -394
|
@@ -2,10 +2,14 @@ import {
|
|
|
2
2
|
HOST_SETTING_KEYS,
|
|
3
3
|
isQueueMode,
|
|
4
4
|
isThinkingLevel
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-sygeb9qb.js";
|
|
6
|
+
|
|
7
|
+
// packages/protocol/src/auth.ts
|
|
8
|
+
var CUSTOM_PROVIDER_APIS = ["openai-completions", "openai-responses", "anthropic-messages", "google-generative-ai"];
|
|
9
|
+
var isCustomProviderApi = (value) => CUSTOM_PROVIDER_APIS.includes(value);
|
|
6
10
|
|
|
7
11
|
// packages/protocol/src/frames.ts
|
|
8
|
-
var HOST_STREAMS = { conversations: 0, index: 0, devices: 0, mcp: 0, models: 1, auth: 1, settings: 2, agents: 6 };
|
|
12
|
+
var HOST_STREAMS = { conversations: 0, index: 0, devices: 0, mcp: 0, models: 1, auth: 1, settings: 2, agents: 6, providers: 21 };
|
|
9
13
|
var HOST_STREAM_NAMES = Object.keys(HOST_STREAMS);
|
|
10
14
|
function hostStreams(hostMinor) {
|
|
11
15
|
return HOST_STREAM_NAMES.filter((name) => HOST_STREAMS[name] <= hostMinor);
|
|
@@ -14,7 +18,7 @@ var conversationStream = (id) => `conversation:${id}`;
|
|
|
14
18
|
var docStream = (kind, id) => `doc:${kind}:${id}`;
|
|
15
19
|
var UNAUTHORIZED_CLOSE_CODE = 4401;
|
|
16
20
|
var INVALID_FRAME_CLOSE_CODE = 4400;
|
|
17
|
-
var PROTOCOL = { major: 5, minor:
|
|
21
|
+
var PROTOCOL = { major: 5, minor: 25 };
|
|
18
22
|
var CALL_SINCE = {
|
|
19
23
|
addProject: 0,
|
|
20
24
|
removeProject: 0,
|
|
@@ -26,6 +30,7 @@ var CALL_SINCE = {
|
|
|
26
30
|
archive: 0,
|
|
27
31
|
renameConversation: 4,
|
|
28
32
|
submit: 0,
|
|
33
|
+
submission: 25,
|
|
29
34
|
withdrawQueued: 14,
|
|
30
35
|
steerQueued: 14,
|
|
31
36
|
abort: 0,
|
|
@@ -40,6 +45,12 @@ var CALL_SINCE = {
|
|
|
40
45
|
createPairing: 0,
|
|
41
46
|
revokeDevice: 0,
|
|
42
47
|
logout: 1,
|
|
48
|
+
beginLogin: 21,
|
|
49
|
+
answerLogin: 21,
|
|
50
|
+
cancelLogin: 21,
|
|
51
|
+
saveCustomProvider: 21,
|
|
52
|
+
removeCustomProvider: 21,
|
|
53
|
+
listEndpointModels: 21,
|
|
43
54
|
updateSettings: 2,
|
|
44
55
|
createAgent: 6,
|
|
45
56
|
updateAgent: 6,
|
|
@@ -47,7 +58,9 @@ var CALL_SINCE = {
|
|
|
47
58
|
setPushToken: 7,
|
|
48
59
|
image: 15,
|
|
49
60
|
beginAttachment: 19,
|
|
50
|
-
appendAttachment: 19
|
|
61
|
+
appendAttachment: 19,
|
|
62
|
+
rtcOffer: 23,
|
|
63
|
+
ping: 23
|
|
51
64
|
};
|
|
52
65
|
function hostSupportsCall(hostMinor, method) {
|
|
53
66
|
return hostMinor >= CALL_SINCE[method];
|
|
@@ -61,7 +74,10 @@ var FEATURE_SINCE = {
|
|
|
61
74
|
hostName: 12,
|
|
62
75
|
imageRefs: 15,
|
|
63
76
|
boundSkills: 16,
|
|
64
|
-
attachments: 19
|
|
77
|
+
attachments: 19,
|
|
78
|
+
imageVariants: 20,
|
|
79
|
+
rtc: 23,
|
|
80
|
+
submitEntry: 25
|
|
65
81
|
};
|
|
66
82
|
function hostSupportsFeature(hostMinor, feature) {
|
|
67
83
|
return hostMinor >= FEATURE_SINCE[feature];
|
|
@@ -104,6 +120,7 @@ var SETTING_VALIDATORS = {
|
|
|
104
120
|
steeringMode: (value) => value === null || isQueueMode(value),
|
|
105
121
|
followUpMode: (value) => value === null || isQueueMode(value),
|
|
106
122
|
acceptAccountDevices: (value) => value === null || typeof value === "boolean",
|
|
123
|
+
rtcStun: (value) => value === null || Array.isArray(value) && value.every(nonempty),
|
|
107
124
|
hostName: (value) => value === null || typeof value === "string"
|
|
108
125
|
};
|
|
109
126
|
function validCallArgs(method, args) {
|
|
@@ -116,6 +133,8 @@ function validCallArgs(method, args) {
|
|
|
116
133
|
switch (method) {
|
|
117
134
|
case "submit":
|
|
118
135
|
return conversationId() && typeof args.text === "string" && (args.whenBusy === "steer" || args.whenBusy === "followUp") && nonempty(args.requestId) && images() && skills() && attachments();
|
|
136
|
+
case "submission":
|
|
137
|
+
return conversationId() && nonempty(args.requestId);
|
|
119
138
|
case "withdrawQueued":
|
|
120
139
|
case "steerQueued":
|
|
121
140
|
return conversationId() && submissionId();
|
|
@@ -140,7 +159,7 @@ function validCallArgs(method, args) {
|
|
|
140
159
|
case "changes":
|
|
141
160
|
return conversationId();
|
|
142
161
|
case "image":
|
|
143
|
-
return nonempty(args.ref);
|
|
162
|
+
return nonempty(args.ref) && (args.variant === undefined || args.variant === "thumbnail" || args.variant === "preview");
|
|
144
163
|
case "beginAttachment":
|
|
145
164
|
return nonempty(args.name) && typeof args.size === "number" && Number.isSafeInteger(args.size) && args.size >= 0;
|
|
146
165
|
case "appendAttachment":
|
|
@@ -162,10 +181,25 @@ function validCallArgs(method, args) {
|
|
|
162
181
|
return conversationId() && typeof args.title === "string";
|
|
163
182
|
case "createPairing":
|
|
164
183
|
return Object.keys(args).length === 0;
|
|
184
|
+
case "rtcOffer":
|
|
185
|
+
return nonempty(args.attempt) && nonempty(args.sdp);
|
|
186
|
+
case "ping":
|
|
187
|
+
return nonempty(args.id);
|
|
165
188
|
case "revokeDevice":
|
|
166
189
|
return nonempty(args.publicKey) && /^[A-Za-z0-9_-]+$/.test(args.publicKey);
|
|
167
190
|
case "logout":
|
|
191
|
+
case "removeCustomProvider":
|
|
168
192
|
return nonempty(args.providerId);
|
|
193
|
+
case "beginLogin":
|
|
194
|
+
return nonempty(args.providerId) && (args.method === "oauth" || args.method === "api_key");
|
|
195
|
+
case "answerLogin":
|
|
196
|
+
return nonempty(args.loginId) && typeof args.promptId === "number" && Number.isSafeInteger(args.promptId) && typeof args.value === "string";
|
|
197
|
+
case "cancelLogin":
|
|
198
|
+
return nonempty(args.loginId);
|
|
199
|
+
case "saveCustomProvider":
|
|
200
|
+
return record(args.provider) && nonempty(args.provider.id) && typeof args.provider.name === "string" && isCustomProviderApi(args.provider.api) && typeof args.provider.baseUrl === "string" && Array.isArray(args.provider.models) && args.provider.models.every((model) => typeof model === "string") && (args.apiKey === undefined || args.apiKey === null || typeof args.apiKey === "string");
|
|
201
|
+
case "listEndpointModels":
|
|
202
|
+
return isCustomProviderApi(args.api) && typeof args.baseUrl === "string" && (args.apiKey === undefined || typeof args.apiKey === "string") && (args.providerId === undefined || nonempty(args.providerId));
|
|
169
203
|
case "createAgent":
|
|
170
204
|
case "updateAgent":
|
|
171
205
|
return role();
|
|
@@ -184,6 +218,175 @@ function nonempty(value) {
|
|
|
184
218
|
return typeof value === "string" && value.length > 0;
|
|
185
219
|
}
|
|
186
220
|
|
|
221
|
+
// packages/protocol/src/rtc.ts
|
|
222
|
+
function rtcAddressScope(address) {
|
|
223
|
+
const ip = parseRtcAddress(address);
|
|
224
|
+
if (ip === undefined)
|
|
225
|
+
return "unknown";
|
|
226
|
+
if (ip.family === 4)
|
|
227
|
+
return ipv4Scope(ip.octets);
|
|
228
|
+
const octets = mappedIpv4(ip.segments);
|
|
229
|
+
return octets === undefined ? ipv6Scope(ip.segments) : ipv4Scope(octets);
|
|
230
|
+
}
|
|
231
|
+
function rtcCandidateEnd(info) {
|
|
232
|
+
return {
|
|
233
|
+
...info.type === undefined ? {} : { type: info.type },
|
|
234
|
+
...info.address === undefined ? {} : { address: info.address },
|
|
235
|
+
scope: rtcAddressScope(info.address)
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function rtcSelectedPair(local, remote) {
|
|
239
|
+
return { local: rtcCandidateEnd(local), remote: rtcCandidateEnd(remote) };
|
|
240
|
+
}
|
|
241
|
+
function parseRtcAddress(address) {
|
|
242
|
+
if (address === undefined)
|
|
243
|
+
return;
|
|
244
|
+
const text = address.trim();
|
|
245
|
+
if (text === "")
|
|
246
|
+
return;
|
|
247
|
+
let host = text;
|
|
248
|
+
if (text.startsWith("[")) {
|
|
249
|
+
const close = text.indexOf("]");
|
|
250
|
+
if (close === -1)
|
|
251
|
+
return;
|
|
252
|
+
const tail = text.slice(close + 1);
|
|
253
|
+
if (tail !== "" && !/^:\d+$/.test(tail))
|
|
254
|
+
return;
|
|
255
|
+
host = text.slice(1, close);
|
|
256
|
+
} else if (text.split(":").length === 2) {
|
|
257
|
+
const [candidate, port] = text.split(":");
|
|
258
|
+
if (/^\d+$/.test(port))
|
|
259
|
+
host = candidate;
|
|
260
|
+
}
|
|
261
|
+
const zone = host.indexOf("%");
|
|
262
|
+
if (zone !== -1)
|
|
263
|
+
host = host.slice(0, zone);
|
|
264
|
+
const ipv4 = parseIpv4(host);
|
|
265
|
+
if (ipv4 !== undefined)
|
|
266
|
+
return { family: 4, octets: ipv4 };
|
|
267
|
+
const ipv6 = parseIpv6(host);
|
|
268
|
+
return ipv6 === undefined ? undefined : { family: 6, segments: ipv6 };
|
|
269
|
+
}
|
|
270
|
+
function parseIpv4(text) {
|
|
271
|
+
const parts = text.split(".");
|
|
272
|
+
if (parts.length !== 4)
|
|
273
|
+
return;
|
|
274
|
+
const octets = [];
|
|
275
|
+
for (const part of parts) {
|
|
276
|
+
if (!/^(?:0|[1-9]\d{0,2})$/.test(part))
|
|
277
|
+
return;
|
|
278
|
+
const value = Number(part);
|
|
279
|
+
if (value > 255)
|
|
280
|
+
return;
|
|
281
|
+
octets.push(value);
|
|
282
|
+
}
|
|
283
|
+
return octets;
|
|
284
|
+
}
|
|
285
|
+
function parseIpv6(text) {
|
|
286
|
+
const halves = text.split("::");
|
|
287
|
+
if (halves.length > 2)
|
|
288
|
+
return;
|
|
289
|
+
const parsePart = (part, allowIpv4) => {
|
|
290
|
+
if (part === "")
|
|
291
|
+
return [];
|
|
292
|
+
const tokens = part.split(":");
|
|
293
|
+
const groups = [];
|
|
294
|
+
for (let index = 0;index < tokens.length; index++) {
|
|
295
|
+
const token = tokens[index];
|
|
296
|
+
if (allowIpv4 && index === tokens.length - 1 && token.includes(".")) {
|
|
297
|
+
const v4 = parseIpv4(token);
|
|
298
|
+
if (v4 === undefined)
|
|
299
|
+
return;
|
|
300
|
+
groups.push(v4[0] << 8 | v4[1], v4[2] << 8 | v4[3]);
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (!/^[0-9a-fA-F]{1,4}$/.test(token))
|
|
304
|
+
return;
|
|
305
|
+
groups.push(Number.parseInt(token, 16));
|
|
306
|
+
}
|
|
307
|
+
return groups;
|
|
308
|
+
};
|
|
309
|
+
if (halves.length === 1) {
|
|
310
|
+
const groups = parsePart(text, true);
|
|
311
|
+
return groups?.length === 8 ? groups : undefined;
|
|
312
|
+
}
|
|
313
|
+
const left = parsePart(halves[0], false);
|
|
314
|
+
const right = parsePart(halves[1], true);
|
|
315
|
+
if (left === undefined || right === undefined)
|
|
316
|
+
return;
|
|
317
|
+
const zeros = 8 - left.length - right.length;
|
|
318
|
+
if (zeros < 1)
|
|
319
|
+
return;
|
|
320
|
+
return [...left, ...new Array(zeros).fill(0), ...right];
|
|
321
|
+
}
|
|
322
|
+
function mappedIpv4(segments) {
|
|
323
|
+
if (!segments.slice(0, 5).every((group) => group === 0) || segments[5] !== 65535)
|
|
324
|
+
return;
|
|
325
|
+
return [segments[6] >> 8, segments[6] & 255, segments[7] >> 8, segments[7] & 255];
|
|
326
|
+
}
|
|
327
|
+
function ipv4Scope(octets) {
|
|
328
|
+
const [a, b] = octets;
|
|
329
|
+
if (a === 127)
|
|
330
|
+
return "loopback";
|
|
331
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
332
|
+
return "tailnet";
|
|
333
|
+
if (a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 169 && b === 254) {
|
|
334
|
+
return "lan";
|
|
335
|
+
}
|
|
336
|
+
return "public";
|
|
337
|
+
}
|
|
338
|
+
function ipv6Scope(segments) {
|
|
339
|
+
if (segments[0] === 64890 && segments[1] === 4444 && segments[2] === 41440)
|
|
340
|
+
return "tailnet";
|
|
341
|
+
if ((segments[0] & 65024) === 64512 || (segments[0] & 65472) === 65152)
|
|
342
|
+
return "lan";
|
|
343
|
+
if (segments.slice(0, 7).every((group) => group === 0) && segments[7] === 1)
|
|
344
|
+
return "loopback";
|
|
345
|
+
return "public";
|
|
346
|
+
}
|
|
347
|
+
var RTC_DEFAULT_STUN = ["stun:stun.cloudflare.com:3478"];
|
|
348
|
+
var RTC_DATA_CHANNEL_LABEL = "pinomad";
|
|
349
|
+
var RTC_MAX_RECORD_BYTES = 65535;
|
|
350
|
+
var RTC_DEFAULT_MAX_MESSAGE_SIZE = 65536;
|
|
351
|
+
var RTC_MIN_MAX_MESSAGE_SIZE = RTC_MAX_RECORD_BYTES;
|
|
352
|
+
function isRtcHello(value) {
|
|
353
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
354
|
+
return false;
|
|
355
|
+
const hello = value;
|
|
356
|
+
return Array.isArray(hello.stun) && hello.stun.every((server) => typeof server === "string" && server.length > 0) && hello.signalOnly === true;
|
|
357
|
+
}
|
|
358
|
+
function rtcCandidateType(candidate) {
|
|
359
|
+
const parts = candidate.trim().split(/\s+/);
|
|
360
|
+
const at = parts.indexOf("typ");
|
|
361
|
+
return at === -1 ? undefined : parts[at + 1];
|
|
362
|
+
}
|
|
363
|
+
function sdpCandidates(sdp) {
|
|
364
|
+
return sdp.split(/\r?\n/).filter((line) => line.startsWith("a=candidate:")).map((line) => line.slice("a=".length));
|
|
365
|
+
}
|
|
366
|
+
function withRtcCandidates(sdp, candidates) {
|
|
367
|
+
const lines = sdp.split(/\r?\n/).filter((line) => line !== "" && !line.startsWith("a=candidate:") && line !== "a=end-of-candidates");
|
|
368
|
+
const unique = [...new Set(candidates.map((candidate) => candidate.replace(/^a=/, "")))];
|
|
369
|
+
return [...lines, ...unique.map((candidate) => `a=${candidate}`), "a=end-of-candidates", ""].join(`\r
|
|
370
|
+
`);
|
|
371
|
+
}
|
|
372
|
+
function parseRtcMaxMessageSize(sdp) {
|
|
373
|
+
const line = sdp.split(/\r?\n/).find((entry) => entry.startsWith("a=max-message-size:"));
|
|
374
|
+
if (line === undefined)
|
|
375
|
+
return { kind: "bounded", bytes: RTC_DEFAULT_MAX_MESSAGE_SIZE };
|
|
376
|
+
const raw = line.slice("a=max-message-size:".length).trim();
|
|
377
|
+
if (!/^\d+$/.test(raw))
|
|
378
|
+
return { kind: "invalid", reason: `malformed a=max-message-size: ${raw}` };
|
|
379
|
+
const bytes = Number(raw);
|
|
380
|
+
if (!Number.isSafeInteger(bytes))
|
|
381
|
+
return { kind: "invalid", reason: `a=max-message-size is not a safe integer: ${raw}` };
|
|
382
|
+
if (bytes === 0)
|
|
383
|
+
return { kind: "unlimited" };
|
|
384
|
+
if (bytes < RTC_MIN_MAX_MESSAGE_SIZE) {
|
|
385
|
+
return { kind: "invalid", reason: `a=max-message-size ${bytes} cannot carry a ${RTC_MIN_MAX_MESSAGE_SIZE}-byte record` };
|
|
386
|
+
}
|
|
387
|
+
return { kind: "bounded", bytes };
|
|
388
|
+
}
|
|
389
|
+
|
|
187
390
|
// packages/protocol/src/attachments.ts
|
|
188
391
|
var MAX_ATTACHMENT_BYTES = 10 * 1024 * 1024;
|
|
189
392
|
var MAX_ATTACHMENTS_PER_MESSAGE = 10;
|
|
@@ -416,4 +619,4 @@ function homeOf(index, summaries, id) {
|
|
|
416
619
|
return;
|
|
417
620
|
}
|
|
418
621
|
|
|
419
|
-
export { HOST_STREAM_NAMES, hostStreams, conversationStream, docStream, UNAUTHORIZED_CLOSE_CODE, INVALID_FRAME_CLOSE_CODE, PROTOCOL, hostSupportsCall, hostSupportsFeature, parseClientFrame, MAX_ATTACHMENTS_PER_MESSAGE, MAX_PENDING_ATTACHMENTS, MAX_PENDING_ATTACHMENT_BYTES, ATTACHMENT_TTL_MS, MAX_ATTACHMENT_NAME_LENGTH, isAttachmentId, base64DecodedLength, attachmentBeginProblem, attachmentChunkProblem, attachmentBlock, parseAttachmentBlock, organize, checkoutOf, homeOf };
|
|
622
|
+
export { isCustomProviderApi, HOST_STREAM_NAMES, hostStreams, conversationStream, docStream, UNAUTHORIZED_CLOSE_CODE, INVALID_FRAME_CLOSE_CODE, PROTOCOL, hostSupportsCall, hostSupportsFeature, parseClientFrame, rtcSelectedPair, RTC_DEFAULT_STUN, RTC_DATA_CHANNEL_LABEL, RTC_MAX_RECORD_BYTES, isRtcHello, rtcCandidateType, sdpCandidates, withRtcCandidates, parseRtcMaxMessageSize, MAX_ATTACHMENTS_PER_MESSAGE, MAX_PENDING_ATTACHMENTS, MAX_PENDING_ATTACHMENT_BYTES, ATTACHMENT_TTL_MS, MAX_ATTACHMENT_NAME_LENGTH, isAttachmentId, base64DecodedLength, attachmentBeginProblem, attachmentChunkProblem, attachmentBlock, parseAttachmentBlock, organize, checkoutOf, homeOf };
|