openmates 0.27.1 → 0.28.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 CHANGED
@@ -121,8 +121,13 @@ openmates chats share last --expires 604800
121
121
  openmates chats delete <chat-id> --yes
122
122
  ```
123
123
 
124
- Exports include YAML and Markdown. When present, code embeds and video
125
- transcripts are written as separate files.
124
+ Exports include YAML and Markdown. When present, code embeds, video transcripts,
125
+ and calendar events are written as separate files. Calendar files appear in
126
+ `calendar/` as `.ics` files, each containing only its primary event.
127
+
128
+ The TypeScript SDK also supports local calendar export from decrypted embed
129
+ content. `buildCalendarEventFile(embedId, content)` returns an RFC 5545 string;
130
+ it requires a valid finished calendar event and does not contact a calendar provider.
126
131
 
127
132
  ### Use apps and typed commands
128
133
 
@@ -337,13 +342,14 @@ Treat these files like account credentials.
337
342
 
338
343
  ## Node.js SDK
339
344
 
340
- The npm package also exports an API-key SDK for Node.js. Use it when you want a
341
- programmatic client instead of shell commands.
345
+ The npm package also exports a Node.js SDK. Without an API key, supported app
346
+ skills and `chats.send()` use the shared free allowance without an account.
347
+ A configured `OPENMATES_API_KEY` automatically uses your account instead.
342
348
 
343
- ```ts
349
+ ```typescript
344
350
  import { OpenMates } from "openmates";
345
351
 
346
- const om = new OpenMates({ apiKey: process.env.OPENMATES_API_KEY });
352
+ const om = new OpenMates();
347
353
 
348
354
  const search = await om.apps.web.search({
349
355
  requests: [{ query: "OpenMates SDK examples" }],
@@ -352,17 +358,31 @@ const search = await om.apps.web.search({
352
358
  const response = await om.chats.send("Summarize this release note draft.");
353
359
  ```
354
360
 
355
- SDK chats are non-persistent by default. Use `saveToAccount: true` only when you
356
- intentionally want the chat saved to the OpenMates account.
361
+ Check the allowance with `await om.anonymous.status()`. No identity setup is
362
+ needed. Free usage is limited by public IP and a shared daily, weekly, and monthly
363
+ service allowance; people on the same network may share the IP limit.
364
+ When the allowance is used or unavailable, `OpenMatesSignupRequiredError` exposes
365
+ `signupRequired`, `code`, and `reason`, with a message directing users to create
366
+ an account. Provider and network failures remain separate errors.
367
+
368
+ Pass `apiKey` or set `OPENMATES_API_KEY` to use an account. Invalid or exhausted
369
+ keys never fall back to free usage. For advanced free-only use, `anonymous: true`
370
+ ignores an environment key; it cannot be combined with an explicit `apiKey`.
371
+
372
+ SDK chats are non-persistent by default. Account-only options, including saved
373
+ chats, Memories, Teams, focus modes, and model overrides, require an API key.
374
+ Use `chats.send()` for anonymous AI chat with automatic routing; `apps.ai.ask()`
375
+ requires an account. With an API key, use `saveToAccount: true` when you want the
376
+ chat saved to the OpenMates account.
357
377
 
358
378
  ## Versioning
359
379
 
360
- OpenMates shows the short product line, for example `v0.27`, in the web app.
380
+ OpenMates shows the short product line, for example `v0.28`, in the web app.
361
381
  The npm package uses exact artifact versions:
362
382
 
363
- - `0.27.0-alpha.N` is a prerelease from the `dev` branch published under the
383
+ - `0.28.0-alpha.N` is a prerelease from the `dev` branch published under the
364
384
  `alpha` npm tag.
365
- - `0.27.0` is a stable release from `main` published under the `latest` npm tag.
385
+ - `0.28.0` is a stable release from `main` published under the `latest` npm tag.
366
386
 
367
387
  Install stable releases with `npm install -g openmates`. Install prereleases with
368
388
  `npm install -g openmates@alpha`.
@@ -374,3 +394,36 @@ Install stable releases with `npm install -g openmates`. Install prereleases wit
374
394
  - [SDK guide](https://openmates.org/docs/user-guide/developers/sdk)
375
395
  - [Source code](https://github.com/glowingkitty/OpenMates/tree/dev/frontend/packages/openmates-cli)
376
396
  - [Issue tracker](https://github.com/glowingkitty/OpenMates/issues)
397
+ ## Experimental image decisions
398
+
399
+ `apps.ai.decide` supports Jev 1.14 decisions over inline PNG/JPEG images:
400
+
401
+ ```typescript
402
+ import { readFile } from "node:fs/promises";
403
+ import { randomUUID } from "node:crypto";
404
+ import { OpenMates } from "openmates";
405
+
406
+ const om = new OpenMates();
407
+ const ref = randomUUID();
408
+ const result = await om.apps.ai.decide({
409
+ state: { board: ref },
410
+ questions: { clear: { type: "noul", instructions: "Is the path to the exit clear?" } },
411
+ assets: {
412
+ [ref]: { type: "image", format: "png", data_base64: (await readFile("board.png")).toString("base64") }
413
+ }
414
+ });
415
+ console.log(result);
416
+ ```
417
+
418
+ Every asset needs a canonical lowercase UUID key referenced in a string value
419
+ in state, instructions, or criteria. OpenMates validates up to four still images,
420
+ 1 MiB decoded per image and 4 MiB total, with both dimensions 280–1568 pixels.
421
+ State/questions remain limited to 16 KiB; the complete JSON body is limited to
422
+ 6 MiB. Use `jpg` or `jpeg` for JPEG bytes. CLI callers can pass the same JSON
423
+ shape with `openmates apps ai decide --input '<JSON>' --json`.
424
+
425
+ This prototype feature calls TypeSafe directly, never OpenRouter fallback.
426
+ Image tokens use the same applicable free allowance or account credits. No
427
+ provider key or image header is required from the caller. It does not fetch
428
+ remote images or create stored uploads; account upload flows still need signup.
429
+ See [REST image decisions](https://github.com/glowingkitty/OpenMates/blob/dev/docs/architecture/apps/rest-api.md#experimental-image-decisions).
@@ -181,22 +181,47 @@ async function recoveryEnvelopeKey(sharedSecret, associatedData) {
181
181
  const salt = await sha256(new TextEncoder().encode("openmates:chat-recovery-envelope:v1"));
182
182
  return hkdfSha256(sharedSecret, salt, await sha256(associatedData));
183
183
  }
184
+ async function openChatMetadataRecoveryEnvelope(envelope, options) {
185
+ if (options.stage !== "initial" && options.stage !== "postprocessing") throw new Error("unsupported metadata stage");
186
+ const aad = concatBytes(
187
+ new TextEncoder().encode("OMCM1"),
188
+ ...[options.ownerId, options.chatId, options.taskId, options.jobId].map((value) => lengthPrefix(canonicalUuid(value, "metadata_identity"))),
189
+ lengthPrefix(new TextEncoder().encode(options.stage)),
190
+ recoveryKeyVersion(options.keyVersion)
191
+ );
192
+ const bytes = await openRecoveryEnvelopeWithAssociatedData(envelope, options.recoveryPrivateKey, aad, 64 * 1024);
193
+ const payload = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
194
+ const identity = {
195
+ owner_id: options.ownerId,
196
+ chat_id: options.chatId,
197
+ task_id: options.taskId,
198
+ job_id: options.jobId,
199
+ stage: options.stage,
200
+ key_version: options.keyVersion
201
+ };
202
+ if (!payload || typeof payload !== "object" || Array.isArray(payload) || Object.keys(payload).sort().join(",") !== [...Object.keys(identity), "metadata"].sort().join(",") || Object.entries(identity).some(([key, value]) => payload[key] !== value)) throw new Error("metadata identity mismatch");
203
+ const metadata = payload.metadata;
204
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata) || !Object.keys(metadata).length || Object.entries(metadata).some(([key, value]) => !["title", "summary", "category", "icon"].includes(key) || typeof value !== "string" || !value.length)) throw new Error("invalid metadata fields");
205
+ return metadata;
206
+ }
184
207
  async function openChatCompletionRecoveryEnvelope(envelope, options) {
208
+ return openRecoveryEnvelopeWithAssociatedData(envelope, options.recoveryPrivateKey, recoveryAssociatedData(options), CHAT_RECOVERY_MAX_PAYLOAD_BYTES);
209
+ }
210
+ async function openRecoveryEnvelopeWithAssociatedData(envelope, recoveryPrivateKeyEncoded, associatedData, maxPayloadBytes) {
185
211
  if (!envelope || Object.keys(envelope).sort().join(",") !== "ciphertext,epk,nonce,v" || envelope.v !== CHAT_RECOVERY_PROTOCOL_VERSION) {
186
212
  throw new Error("invalid recovery envelope fields or version");
187
213
  }
188
214
  const recoveryPrivateKey = base64UrlToBytes(
189
- options.recoveryPrivateKey,
215
+ recoveryPrivateKeyEncoded,
190
216
  "recovery_private_key",
191
217
  CHAT_RECOVERY_KEY_BYTES
192
218
  );
193
219
  const ephemeralPublicKey = base64UrlToBytes(envelope.epk, "epk", CHAT_RECOVERY_KEY_BYTES);
194
220
  const nonce = base64UrlToBytes(envelope.nonce, "nonce", CHAT_RECOVERY_NONCE_BYTES);
195
221
  const ciphertext = base64UrlToBytes(envelope.ciphertext, "ciphertext");
196
- if (ciphertext.length < 16 || ciphertext.length - 16 > CHAT_RECOVERY_MAX_PAYLOAD_BYTES) {
222
+ if (ciphertext.length < 16 || ciphertext.length - 16 > maxPayloadBytes) {
197
223
  throw new Error("ciphertext payload size is invalid");
198
224
  }
199
- const associatedData = recoveryAssociatedData(options);
200
225
  const sharedSecret = nacl.scalarMult(recoveryPrivateKey, ephemeralPublicKey);
201
226
  const envelopeKeyBytes = await recoveryEnvelopeKey(sharedSecret, associatedData);
202
227
  const envelopeKey = await cryptoApi.subtle.importKey(
@@ -1289,6 +1314,7 @@ export {
1289
1314
  bytesToBase64Url,
1290
1315
  deriveTeamInviteKey,
1291
1316
  deriveChatCompletionRecoveryKeypair,
1317
+ openChatMetadataRecoveryEnvelope,
1292
1318
  openChatCompletionRecoveryEnvelope,
1293
1319
  openRecoveryOutputEnvelope,
1294
1320
  hashEmail,