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.
@@ -2,10 +2,14 @@ import {
2
2
  HOST_SETTING_KEYS,
3
3
  isQueueMode,
4
4
  isThinkingLevel
5
- } from "./chunk-znhn8af7.js";
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: 19 };
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 };