spectrum-ts 2.0.0 → 3.0.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.
Files changed (32) hide show
  1. package/dist/{attachment-B4nSrKVd.d.ts → attachment-WePAHfcH.d.ts} +1 -1
  2. package/dist/{authoring-BjE5BvlO.d.ts → authoring-DDh3muGT.d.ts} +61 -26
  3. package/dist/authoring.d.ts +3 -3
  4. package/dist/authoring.js +5 -5
  5. package/dist/{chunk-NNY6LMSC.js → chunk-77U6SH5A.js} +1 -1
  6. package/dist/{chunk-ATNAE7OR.js → chunk-AYCMTRVC.js} +549 -76
  7. package/dist/{chunk-6BI4PFTP.js → chunk-CHY5YLLV.js} +1 -1
  8. package/dist/{chunk-U3LXXT3W.js → chunk-EZ5SNNFS.js} +20 -8
  9. package/dist/{chunk-WXY5QP3M.js → chunk-FULEQIRQ.js} +27 -21
  10. package/dist/{chunk-2ILTJC35.js → chunk-LQMDV75O.js} +205 -11
  11. package/dist/{chunk-NGC4DJIX.js → chunk-LX437ZTY.js} +416 -135
  12. package/dist/{chunk-3B4QH4JG.js → chunk-MHGCPC2V.js} +1 -1
  13. package/dist/{chunk-U7AWXDH6.js → chunk-NZ5WCMTY.js} +1 -1
  14. package/dist/{chunk-5LT5J3NR.js → chunk-TXRWKSNH.js} +262 -30
  15. package/dist/{chunk-Q537JPTG.js → chunk-UXJ5OO6P.js} +10 -10
  16. package/dist/index.d.ts +107 -56
  17. package/dist/index.js +29 -182
  18. package/dist/providers/imessage/index.d.ts +6 -14
  19. package/dist/providers/imessage/index.js +6 -6
  20. package/dist/providers/index.d.ts +3 -3
  21. package/dist/providers/index.js +11 -11
  22. package/dist/providers/slack/index.d.ts +1 -2
  23. package/dist/providers/slack/index.js +3 -3
  24. package/dist/providers/telegram/index.d.ts +3 -5
  25. package/dist/providers/telegram/index.js +5 -5
  26. package/dist/providers/terminal/index.d.ts +2 -4
  27. package/dist/providers/terminal/index.js +5 -5
  28. package/dist/providers/whatsapp-business/index.d.ts +1 -1
  29. package/dist/providers/whatsapp-business/index.js +4 -4
  30. package/dist/{types-BD0-kKyv.d.ts → types-BujGKBin.d.ts} +1 -1
  31. package/dist/{types-Bje8aq1k.d.ts → types-YqCNUDIt.d.ts} +171 -23
  32. package/package.json +2 -1
@@ -4,7 +4,7 @@ import {
4
4
  fetchUrlBytes,
5
5
  readSchema,
6
6
  streamSchema
7
- } from "./chunk-2ILTJC35.js";
7
+ } from "./chunk-LQMDV75O.js";
8
8
 
9
9
  // src/content/richlink.ts
10
10
  import z from "zod";
@@ -1,10 +1,10 @@
1
1
  import { createRequire as __spectrumCreateRequire } from "node:module"; const require = __spectrumCreateRequire(import.meta.url);
2
2
  import {
3
3
  asVoice
4
- } from "./chunk-NNY6LMSC.js";
4
+ } from "./chunk-77U6SH5A.js";
5
5
  import {
6
6
  asContact
7
- } from "./chunk-U7AWXDH6.js";
7
+ } from "./chunk-NZ5WCMTY.js";
8
8
  import {
9
9
  stream
10
10
  } from "./chunk-5XEFJBN2.js";
@@ -15,12 +15,12 @@ import {
15
15
  import {
16
16
  UnsupportedError,
17
17
  definePlatform
18
- } from "./chunk-NGC4DJIX.js";
18
+ } from "./chunk-LX437ZTY.js";
19
19
  import {
20
20
  asAttachment,
21
21
  asCustom,
22
22
  reactionSchema
23
- } from "./chunk-2ILTJC35.js";
23
+ } from "./chunk-LQMDV75O.js";
24
24
 
25
25
  // src/providers/terminal/index.ts
26
26
  import { spawn } from "child_process";
@@ -781,12 +781,18 @@ var terminal = definePlatform("Terminal", {
781
781
  })
782
782
  },
783
783
  space: {
784
- params: z.object({ id: z.string().optional() }),
785
- resolve: async ({ client, input }) => {
786
- const id = input.params?.id ?? generateChatId(client);
784
+ create: async ({ client }) => {
785
+ const id = generateChatId(client);
787
786
  client.knownChats.add(id);
788
787
  await client.session.request("ensureSpace", { id });
789
788
  return { id };
789
+ },
790
+ // Explicit (not the framework default) so targeting a known id still
791
+ // materializes the chat in the TUI via `ensureSpace`.
792
+ get: async ({ client, input }) => {
793
+ client.knownChats.add(input.id);
794
+ await client.session.request("ensureSpace", { id: input.id });
795
+ return { id: input.id };
790
796
  }
791
797
  },
792
798
  // Return a ManagedStream (not a native async generator): a native generator
@@ -857,7 +863,13 @@ var terminal = definePlatform("Terminal", {
857
863
  messageId: content.target.id,
858
864
  reaction: content.emoji
859
865
  });
860
- return;
866
+ const timestamp = /* @__PURE__ */ new Date();
867
+ return {
868
+ id: `reaction:${content.target.id}:${content.emoji}:${timestamp.toISOString()}`,
869
+ content,
870
+ space: { id: space.id },
871
+ timestamp
872
+ };
861
873
  }
862
874
  if (content.type === "typing") {
863
875
  const method = content.state === "start" ? "startTyping" : "stopTyping";
@@ -9,13 +9,13 @@ import {
9
9
  import {
10
10
  UnsupportedError,
11
11
  definePlatform
12
- } from "./chunk-NGC4DJIX.js";
12
+ } from "./chunk-LX437ZTY.js";
13
13
  import {
14
14
  asAttachment,
15
15
  asCustom,
16
16
  asReaction,
17
17
  asText
18
- } from "./chunk-2ILTJC35.js";
18
+ } from "./chunk-LQMDV75O.js";
19
19
 
20
20
  // src/providers/slack/index.ts
21
21
  import { createClient as createClient2, staticTokens } from "@photon-ai/slack";
@@ -340,13 +340,12 @@ var send = async (client, space, content) => {
340
340
  );
341
341
  }
342
342
  if (content.type === "reaction") {
343
- await reactToMessage(
343
+ return await reactToMessage(
344
344
  client,
345
345
  space,
346
346
  content.target.ts ?? content.target.id,
347
- content.emoji
347
+ content
348
348
  );
349
- return;
350
349
  }
351
350
  if (content.type === "typing") {
352
351
  return;
@@ -388,15 +387,23 @@ var sendContent = async (client, space, content, threadTs) => {
388
387
  throw UnsupportedError.content(content.type);
389
388
  }
390
389
  };
391
- var reactToMessage = async (client, space, targetTs, emoji) => {
390
+ var reactToMessage = async (client, space, targetTs, content) => {
392
391
  await client.team(space.teamId).messages.send({
393
392
  channel: space.id,
394
393
  reaction: {
395
- emoji,
394
+ emoji: content.emoji,
396
395
  itemChannel: space.id,
397
396
  itemTs: targetTs
398
397
  }
399
398
  });
399
+ return {
400
+ id: `${targetTs}:reaction:self:${content.emoji}`,
401
+ content,
402
+ space: { id: space.id, teamId: space.teamId },
403
+ timestamp: /* @__PURE__ */ new Date(),
404
+ ts: targetTs,
405
+ isFromMe: true
406
+ };
400
407
  };
401
408
  var replyToMessage = async (client, space, targetTs, content) => await sendContent(client, space, content, targetTs);
402
409
 
@@ -424,7 +431,6 @@ var spaceSchema = z.object({
424
431
  teamId: z.string()
425
432
  });
426
433
  var spaceParamsSchema = z.object({
427
- channel: z.string().optional(),
428
434
  teamId: z.string()
429
435
  });
430
436
  var messageSchema = z.object({
@@ -475,27 +481,18 @@ var slack = definePlatform("Slack", {
475
481
  space: {
476
482
  schema: spaceSchema,
477
483
  params: spaceParamsSchema,
478
- resolve: async ({ input }) => {
484
+ create: async ({ input }) => {
479
485
  const teamId = input.params?.teamId;
480
486
  if (!teamId) {
481
487
  throw new Error(
482
- "Slack space creation requires a teamId param. Pass it via slack.space({ channel, teamId }) or slack.space([user], { teamId })."
483
- );
484
- }
485
- const channel = input.params?.channel;
486
- if (channel) {
487
- return { id: channel, teamId };
488
- }
489
- if (input.users.length === 0) {
490
- throw new Error(
491
- "Slack space creation requires either a channel param or at least one user"
488
+ "Slack space creation requires a teamId param. Pass it via space.create(user, { teamId })."
492
489
  );
493
490
  }
494
491
  if (input.users.length > 1) {
495
492
  throw UnsupportedError.action(
496
- "createSpace",
493
+ "space.create",
497
494
  "Slack",
498
- "group DMs require an explicit channel id (Slack's conversations.open is not exposed); pass `channel` in params"
495
+ "group DMs require an explicit channel id (Slack's conversations.open is not exposed); use space.get(channelId, { teamId })"
499
496
  );
500
497
  }
501
498
  const user = input.users[0];
@@ -503,6 +500,15 @@ var slack = definePlatform("Slack", {
503
500
  throw new Error("Slack space creation requires a user");
504
501
  }
505
502
  return { id: user.id, teamId };
503
+ },
504
+ get: async ({ input }) => {
505
+ const teamId = input.params?.teamId;
506
+ if (!teamId) {
507
+ throw new Error(
508
+ "Slack spaces require a teamId param. Pass it via space.get(channelId, { teamId })."
509
+ );
510
+ }
511
+ return { id: input.id, teamId };
506
512
  }
507
513
  },
508
514
  message: {
@@ -195,16 +195,202 @@ function custom(raw) {
195
195
  }
196
196
 
197
197
  // src/content/text.ts
198
+ import z5 from "zod";
199
+
200
+ // src/content/stream-text.ts
198
201
  import z4 from "zod";
199
- var textSchema = z4.object({
200
- type: z4.literal("text"),
201
- text: z4.string().nonempty()
202
+ var streamTextSchema = z4.object({
203
+ type: z4.literal("streamText"),
204
+ // A single-consumption producer of normalized text deltas. The builder
205
+ // closes over the normalized source; the platform driver calls it once.
206
+ // Kept opaque to Zod via `z.custom` (same approach as `attachment.read`).
207
+ stream: z4.custom(
208
+ (v) => typeof v === "function",
209
+ {
210
+ message: "streamText.stream must be a function returning AsyncIterable<string>"
211
+ }
212
+ ),
213
+ // How platforms should interpret the accumulated text; absent = plain.
214
+ format: z4.enum(["plain", "markdown"]).optional()
202
215
  });
203
- var asText = (text2) => textSchema.parse({ type: "text", text: text2 });
204
- function text(text2) {
216
+ var asRecord = (value) => typeof value === "object" && value !== null ? value : void 0;
217
+ var SKIP_EVENT_TYPES = /* @__PURE__ */ new Set([
218
+ "message_start",
219
+ "message_delta",
220
+ "message_stop",
221
+ "content_block_start",
222
+ "content_block_stop",
223
+ "ping"
224
+ ]);
225
+ var fromOpenAIResponses = (obj) => {
226
+ const type = obj.type;
227
+ if (typeof type !== "string" || !type.startsWith("response.")) {
228
+ return;
229
+ }
230
+ if (type === "response.output_text.delta" && typeof obj.delta === "string") {
231
+ return obj.delta;
232
+ }
233
+ return null;
234
+ };
235
+ var fromAnthropicDelta = (obj) => {
236
+ if (obj.type !== "content_block_delta") {
237
+ return;
238
+ }
239
+ const delta = asRecord(obj.delta);
240
+ if (delta?.type === "text_delta" && typeof delta.text === "string") {
241
+ return delta.text;
242
+ }
243
+ return null;
244
+ };
245
+ var fromAiSdkPart = (obj) => {
246
+ if (obj.type !== "text-delta") {
247
+ return;
248
+ }
249
+ if (typeof obj.textDelta === "string") {
250
+ return obj.textDelta;
251
+ }
252
+ return typeof obj.text === "string" ? obj.text : null;
253
+ };
254
+ var fromOpenAIChat = (obj) => {
255
+ if (!Array.isArray(obj.choices)) {
256
+ return;
257
+ }
258
+ const delta = asRecord(asRecord(obj.choices[0])?.delta);
259
+ const content = delta?.content;
260
+ return typeof content === "string" ? content : null;
261
+ };
262
+ var fromControlEvent = (obj) => typeof obj.type === "string" && SKIP_EVENT_TYPES.has(obj.type) ? null : void 0;
263
+ var OBJECT_EXTRACTORS = [
264
+ fromOpenAIResponses,
265
+ fromAnthropicDelta,
266
+ fromAiSdkPart,
267
+ fromOpenAIChat,
268
+ fromControlEvent
269
+ ];
270
+ var defaultExtract = (chunk) => {
271
+ if (typeof chunk === "string") {
272
+ return chunk;
273
+ }
274
+ const record = asRecord(chunk);
275
+ if (!record) {
276
+ throw new Error(
277
+ `text stream: cannot extract a text delta from a ${typeof chunk} chunk. Pass { extract } to map your stream's chunks to text.`
278
+ );
279
+ }
280
+ for (const extractor of OBJECT_EXTRACTORS) {
281
+ const result = extractor(record);
282
+ if (result !== void 0) {
283
+ return result;
284
+ }
285
+ }
286
+ throw new Error(
287
+ `text stream: unrecognized chunk shape (type=${String(record.type)}). Pass an { extract } function to map your provider's chunk to a text delta.`
288
+ );
289
+ };
290
+ var isReadableStream = (value) => typeof value?.getReader === "function";
291
+ var isAsyncIterable = (value) => typeof value?.[Symbol.asyncIterator] === "function";
292
+ async function* readableToAsync(source) {
293
+ if (isAsyncIterable(source)) {
294
+ yield* source;
295
+ return;
296
+ }
297
+ const reader = source.getReader();
298
+ try {
299
+ while (true) {
300
+ const { done, value } = await reader.read();
301
+ if (done) {
302
+ return;
303
+ }
304
+ yield value;
305
+ }
306
+ } finally {
307
+ reader.releaseLock();
308
+ }
309
+ }
310
+ var resolveChunkIterable = (source) => {
311
+ const textStream = source.textStream;
312
+ if (textStream != null) {
313
+ if (isReadableStream(textStream)) {
314
+ return readableToAsync(textStream);
315
+ }
316
+ if (isAsyncIterable(textStream)) {
317
+ return textStream;
318
+ }
319
+ throw new Error(
320
+ "text stream: `.textStream` must be an AsyncIterable or a ReadableStream."
321
+ );
322
+ }
323
+ if (isReadableStream(source)) {
324
+ return readableToAsync(source);
325
+ }
326
+ if (isAsyncIterable(source)) {
327
+ return source;
328
+ }
329
+ throw new Error(
330
+ "text stream: source must be an AsyncIterable, a ReadableStream, or an object with a `.textStream` (e.g. the AI SDK streamText() result)."
331
+ );
332
+ };
333
+ var StreamConsumedError = class extends Error {
334
+ constructor() {
335
+ super(
336
+ "text stream: this source has already been consumed \u2014 a stream can only be sent once."
337
+ );
338
+ this.name = "StreamConsumedError";
339
+ }
340
+ };
341
+ var normalize = (source, options) => {
342
+ const extract = options?.extract ? options.extract : defaultExtract;
343
+ let consumed = false;
344
+ return async function* normalized() {
345
+ if (consumed) {
346
+ throw new StreamConsumedError();
347
+ }
348
+ consumed = true;
349
+ for await (const chunk of resolveChunkIterable(source)) {
350
+ const delta = extract(chunk);
351
+ if (delta) {
352
+ yield delta;
353
+ }
354
+ }
355
+ };
356
+ };
357
+ var asStreamText = (input) => streamTextSchema.parse({
358
+ type: "streamText",
359
+ stream: input.stream,
360
+ ...input.format ? { format: input.format } : {}
361
+ });
362
+ var drainStreamText = async (content) => {
363
+ let full = "";
364
+ for await (const delta of content.stream()) {
365
+ full += delta;
366
+ }
367
+ return full;
368
+ };
369
+ var streamTextBuilder = (kind, source, options) => {
370
+ if (typeof source.build === "function") {
371
+ throw new Error(
372
+ `${kind}(): pass the stream source itself (an AsyncIterable, a ReadableStream, or an SDK result with .textStream), not another content builder.`
373
+ );
374
+ }
205
375
  return {
206
- build: async () => asText(text2)
376
+ build: async () => asStreamText({
377
+ stream: normalize(source, options),
378
+ format: kind === "markdown" ? "markdown" : void 0
379
+ })
207
380
  };
381
+ };
382
+
383
+ // src/content/text.ts
384
+ var textSchema = z5.object({
385
+ type: z5.literal("text"),
386
+ text: z5.string().nonempty()
387
+ });
388
+ var asText = (text2) => textSchema.parse({ type: "text", text: text2 });
389
+ function text(source, options) {
390
+ if (typeof source === "string") {
391
+ return { build: async () => asText(source) };
392
+ }
393
+ return streamTextBuilder("text", source, options);
208
394
  }
209
395
 
210
396
  // src/content/resolve.ts
@@ -213,12 +399,12 @@ var resolveContents = (items) => Promise.all(
213
399
  );
214
400
 
215
401
  // src/content/reaction.ts
216
- import z5 from "zod";
402
+ import z6 from "zod";
217
403
  var isMessage = (v) => typeof v === "object" && v !== null && "id" in v && "content" in v;
218
- var reactionSchema = z5.object({
219
- type: z5.literal("reaction"),
220
- emoji: z5.string().min(1),
221
- target: z5.custom(isMessage, {
404
+ var reactionSchema = z6.object({
405
+ type: z6.literal("reaction"),
406
+ emoji: z6.string().min(1),
407
+ target: z6.custom(isMessage, {
222
408
  message: "reaction target must be a Message"
223
409
  })
224
410
  });
@@ -226,6 +412,11 @@ var asReaction = (input) => reactionSchema.parse({ type: "reaction", ...input })
226
412
  function reaction(emoji, target) {
227
413
  return {
228
414
  build: async () => {
415
+ if (!target) {
416
+ throw new Error(
417
+ "reaction() target is undefined \u2014 the targeted message was never sent (space.send resolves undefined when a platform skips unsupported content)"
418
+ );
419
+ }
229
420
  if (target.content.type === "reaction") {
230
421
  throw new Error('reaction() cannot target "reaction" content');
231
422
  }
@@ -250,6 +441,9 @@ export {
250
441
  attachment,
251
442
  asCustom,
252
443
  custom,
444
+ StreamConsumedError,
445
+ drainStreamText,
446
+ streamTextBuilder,
253
447
  textSchema,
254
448
  asText,
255
449
  text,