@stina/extension-api 0.36.0 → 0.51.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.
@@ -633,6 +633,17 @@ interface ProviderDefinition {
633
633
  * to call extension actions (e.g. OAuth, "Test connection").
634
634
  */
635
635
  configView?: ProviderConfigView;
636
+ /**
637
+ * Extra settings shown only for models that report
638
+ * `capabilities.voiceDuplex` — voice selection, transcription model, and
639
+ * whatever else is specific to this provider's realtime backend.
640
+ *
641
+ * Same DSL and same `$settings.<key>` binding as {@link configView}; the host
642
+ * renders it in its own section of the model editor and stores the values in
643
+ * the same settingsOverride. Keep general provider settings in `configView`
644
+ * so they stay visible for models without voice.
645
+ */
646
+ voiceConfigView?: ProviderConfigView;
636
647
  }
637
648
  /**
638
649
  * Component-tree-based configuration view for a provider.
@@ -741,6 +752,21 @@ interface AIProvider {
741
752
  * Optional: Generate embeddings
742
753
  */
743
754
  embed?(texts: string[]): Promise<number[][]>;
755
+ /**
756
+ * Optional: Open a duplex voice session for a model that reports
757
+ * `capabilities.voiceDuplex`.
758
+ *
759
+ * The provider is responsible for authenticating with its backend and
760
+ * returning everything the client needs to connect. Audio flows directly
761
+ * between the client and the provider — it never passes through Stina — but
762
+ * the session is configured here, on the server, so the instructions and
763
+ * tools cannot be tampered with client-side.
764
+ *
765
+ * Implementations must not put long-lived credentials in the returned
766
+ * descriptor. Either complete the handshake here (`webrtc`) or mint a
767
+ * short-lived secret (`websocket`).
768
+ */
769
+ createVoiceSession?(options: VoiceSessionOptions): Promise<VoiceSessionDescriptor>;
744
770
  }
745
771
  /**
746
772
  * Model information
@@ -754,6 +780,113 @@ interface ModelInfo {
754
780
  description?: string;
755
781
  /** Context window size */
756
782
  contextLength?: number;
783
+ /** What this model can do beyond plain text chat */
784
+ capabilities?: ModelCapabilities;
785
+ }
786
+ /**
787
+ * Optional model capabilities.
788
+ *
789
+ * Absent or `false` means "not supported" — a provider that says nothing keeps
790
+ * behaving exactly as before.
791
+ */
792
+ interface ModelCapabilities {
793
+ /**
794
+ * The model can hold a real-time two-way voice conversation, with the user
795
+ * and the model able to speak at the same time.
796
+ *
797
+ * Report this per model *and* per auth mode: the same provider may support
798
+ * voice with one kind of credential and not another.
799
+ */
800
+ voiceDuplex?: boolean;
801
+ /**
802
+ * The model can be shown images alongside the text of a message.
803
+ *
804
+ * Report this per model *and* per auth mode, like `voiceDuplex`: the same
805
+ * provider often serves both a vision model and a text-only one, and a picture
806
+ * sent to the latter is at best ignored and at worst an error mid-conversation.
807
+ * Stina uses it to decide whether the paperclip is offered at all.
808
+ */
809
+ vision?: boolean;
810
+ }
811
+ /**
812
+ * How the client wants to connect to the voice session.
813
+ *
814
+ * Clients ask for the transport they can actually implement — browsers do
815
+ * WebRTC, a native app may prefer a plain socket — and providers implement the
816
+ * ones their backend offers. A provider that is handed a transport it does not
817
+ * support should throw with a message naming the transports it does.
818
+ */
819
+ type VoiceTransportRequest =
820
+ /** Client has created an offer and wants the provider to complete the handshake. */
821
+ {
822
+ transport: 'webrtc';
823
+ sdpOffer: string;
824
+ }
825
+ /** Client will open a socket itself and needs a short-lived credential for it. */
826
+ | {
827
+ transport: 'websocket';
828
+ };
829
+ /**
830
+ * Everything the client needs to connect, and nothing it needs to decide.
831
+ *
832
+ * `sessionConfig` is the provider-shaped session object (instructions, tools,
833
+ * voice, transcription). The client forwards it verbatim once the connection is
834
+ * up — it is a transport, not a decision-maker.
835
+ */
836
+ type VoiceSessionDescriptor = {
837
+ transport: 'webrtc';
838
+ /** Answer to the client's offer. */
839
+ sdpAnswer: string;
840
+ sessionConfig: unknown;
841
+ /** Provider-side ID for the call, useful for logging and teardown. */
842
+ providerSessionId?: string;
843
+ } | {
844
+ transport: 'websocket';
845
+ /** Socket URL to connect to. */
846
+ url: string;
847
+ /** Short-lived credential. Must not be a long-lived API key. */
848
+ clientSecret: string;
849
+ /** ISO timestamp after which `clientSecret` stops working. */
850
+ expiresAt: string;
851
+ sessionConfig: unknown;
852
+ providerSessionId?: string;
853
+ };
854
+ /**
855
+ * Options for opening a voice session.
856
+ *
857
+ * Mirrors {@link ChatOptions} where it can, so a provider that already
858
+ * implements `chat` finds the same shapes here.
859
+ */
860
+ interface VoiceSessionOptions {
861
+ /** How the client wants to connect */
862
+ transport: VoiceTransportRequest;
863
+ /** Model to use — must be one that reported `capabilities.voiceDuplex` */
864
+ model?: string;
865
+ /**
866
+ * System instructions for the session. This is how the assistant keeps her
867
+ * identity in voice mode; providers must pass it through unchanged.
868
+ */
869
+ instructions?: string;
870
+ /** Tools the model may call during the conversation */
871
+ tools?: ToolDefinition[];
872
+ /**
873
+ * The user's language as an ISO-639-1 code, when known.
874
+ *
875
+ * Pass it to the transcription model rather than letting it detect the
876
+ * language per utterance. Detection is unreliable on short or quiet audio and
877
+ * fails in a way that looks like nonsense rather than an error — a Swedish
878
+ * "hej" coming back as Indonesian, for instance.
879
+ */
880
+ language?: string;
881
+ /** Provider-specific settings from model configuration */
882
+ settings?: Record<string, unknown>;
883
+ /** Request context (user info, session metadata — not provider config) */
884
+ context?: {
885
+ userId?: string;
886
+ [key: string]: unknown;
887
+ };
888
+ /** Abort signal for cancellation */
889
+ signal?: AbortSignal;
757
890
  }
758
891
  /**
759
892
  * Chat message
@@ -761,11 +894,37 @@ interface ModelInfo {
761
894
  interface ChatMessage {
762
895
  role: 'user' | 'assistant' | 'system' | 'tool';
763
896
  content: string;
897
+ /**
898
+ * Images the user attached to this message, already decoded and ready to send.
899
+ *
900
+ * Additive: a provider that ignores the field behaves exactly as it did before,
901
+ * which is why the text is still in `content` rather than being moved into a
902
+ * parts array. A provider that supports vision should report
903
+ * `capabilities.vision` and fold these into whatever multimodal shape its API
904
+ * expects.
905
+ *
906
+ * Only ever `image/jpeg` or `image/png` — see `ChatAttachmentDTO` for why.
907
+ */
908
+ images?: ChatImage[];
764
909
  /** For assistant messages: tool calls made by the model */
765
910
  tool_calls?: ToolCall[];
766
911
  /** For tool messages: the ID of the tool call this is a response to */
767
912
  tool_call_id?: string;
768
913
  }
914
+ /**
915
+ * One image on a chat message, carried as bytes rather than as a URL.
916
+ *
917
+ * A URL would have to be reachable *from the provider*, and Stina commonly runs
918
+ * on a home server talking to a model on the same LAN or on the machine next to
919
+ * it. There is no address that is both private enough and reachable enough, so
920
+ * the bytes travel with the request.
921
+ */
922
+ interface ChatImage {
923
+ /** `image/jpeg` or `image/png`. */
924
+ mime: string;
925
+ /** The image itself, base64 with no data-URI prefix. */
926
+ data: string;
927
+ }
769
928
  /**
770
929
  * A tool call made by the model
771
930
  */
@@ -1395,6 +1554,97 @@ interface UserAPI {
1395
1554
  getProfile(): Promise<UserProfile>;
1396
1555
  listIds(): Promise<string[]>;
1397
1556
  }
1557
+ /**
1558
+ * Visual presentation for a conversation Stina starts proactively in response to
1559
+ * an event reported by an extension.
1560
+ *
1561
+ * The extension owns the *chrome* of the list entry — icon, label, framing,
1562
+ * accent and badge — while Stina authors the *content* (title and opening
1563
+ * message). Every field is optional: omitted fields fall back to sensible
1564
+ * defaults, and `icon` falls back to the reporting extension's manifest icon.
1565
+ *
1566
+ * Colour is intentionally a small set of theme tokens rather than free-form so
1567
+ * the conversation list stays visually coherent across light/dark themes.
1568
+ */
1569
+ interface ConversationPresentation {
1570
+ /**
1571
+ * Structural shape of the list entry — *what kind of thing* this is, not how
1572
+ * urgent it is. Urgency is Stina's call alone (see `severityGuidance`).
1573
+ * Defaults to `note`. Each variant renders a different set of fields:
1574
+ * - `note` — icon, title and a two-line preview. The general-purpose default.
1575
+ * - `event` — something happening at a point in time; renders `at` as a time
1576
+ * block with a countdown. Meetings, reminders, deadlines.
1577
+ * - `message` — something a person or system sent; renders `sender` on its own
1578
+ * line with the subject as the title. Mail, chat, comments.
1579
+ * - `digest` — several things collected into one entry; renders `items` as a
1580
+ * short bullet list with a count. Morning briefs, weekly summaries.
1581
+ * - `status` — a transactional outcome; renders `statusLabel` as a pill and no
1582
+ * preview. Finished builds, deliveries, completed jobs.
1583
+ * - `insight` — Stina's own observation or suggestion rather than an extension's
1584
+ * report. Rendered in her own voice with a soft accent panel.
1585
+ *
1586
+ * The variant also picks how the entry is decorated, so that two kinds of card
1587
+ * never resolve to the same picture: `note`, `message` and `status` are drawn
1588
+ * light, on an accent rail, while `event`, `digest` and `insight` sit on a
1589
+ * filled plate. Severity governs how strongly that decoration is drawn, never
1590
+ * which of the two it is.
1591
+ */
1592
+ variant?: 'note' | 'event' | 'message' | 'digest' | 'status' | 'insight';
1593
+ /**
1594
+ * Leading icon, as a Hugeicons name in lower case with hyphens (`sun-03`,
1595
+ * `cloud-angled-rain`). Hugeicons documents its names in PascalCase; those are
1596
+ * accepted and translated, but the hyphenated form is what the icon set uses.
1597
+ * An unknown name falls back to a default rather than rendering blank.
1598
+ * Falls back to the extension's manifest icon when omitted.
1599
+ */
1600
+ icon?: HugeIconName;
1601
+ /** Small eyebrow label above the content, e.g. "Mail" or "Reminder". */
1602
+ label?: LocalizedString;
1603
+ /** Accent colour as a theme token. Defaults to `default`. */
1604
+ accent?: 'default' | 'info' | 'warning' | 'success' | 'danger';
1605
+ /** Optional short badge, e.g. the account a mail arrived in. */
1606
+ badge?: LocalizedString;
1607
+ /**
1608
+ * When the event happens (ISO 8601). Only used by `variant: 'event'`, which
1609
+ * renders it as a time block and derives a relative countdown from it.
1610
+ */
1611
+ at?: string;
1612
+ /** Who sent it. Only used by `variant: 'message'`. */
1613
+ sender?: LocalizedString;
1614
+ /**
1615
+ * The domain the message came from, which lets the list show the sender's own
1616
+ * icon in place of their initials. Only used by `variant: 'message'`.
1617
+ *
1618
+ * Report it in whatever form you hold it — `plex.tv`, `no-reply@plex.tv`, or
1619
+ * `Plex <no-reply@plex.tv>` all work, and the domain is taken out and
1620
+ * normalised for you. Never localised: it is an identifier, not a label.
1621
+ *
1622
+ * Stina fetches the icon herself, from her own server, and caches it. Nothing
1623
+ * about the message is sent anywhere, and no third-party icon service is
1624
+ * consulted; a domain that has no icon simply falls back to the initials.
1625
+ */
1626
+ senderDomain?: string;
1627
+ /**
1628
+ * The collected items. Only used by `variant: 'digest'`; at most three are
1629
+ * rendered, and `count` reports the true total when there are more.
1630
+ */
1631
+ items?: LocalizedString[];
1632
+ /** Total number of collected items, when it exceeds what `items` lists. */
1633
+ count?: number;
1634
+ /** Short outcome label for the pill, e.g. "Delivered". Only used by `variant: 'status'`. */
1635
+ statusLabel?: LocalizedString;
1636
+ /**
1637
+ * Guidance to Stina on how to judge how much this deserves the user's attention
1638
+ * — *not* a severity value. Extensions describe what would make an event
1639
+ * important ("urgent when the sender is on the user's team, or the deadline is
1640
+ * today"); Stina weighs that against everything else she knows and picks the
1641
+ * level herself. She may disregard it entirely.
1642
+ *
1643
+ * Plain text in any language, one or two sentences. It is shown to Stina during
1644
+ * her deliberation and never to the user.
1645
+ */
1646
+ severityGuidance?: string;
1647
+ }
1398
1648
  /**
1399
1649
  * Chat instruction message
1400
1650
  */
@@ -1402,6 +1652,24 @@ interface ChatInstructionMessage {
1402
1652
  text: string;
1403
1653
  conversationId?: string;
1404
1654
  userId?: string;
1655
+ /**
1656
+ * When true, the message is treated as a system event reported by the extension
1657
+ * rather than a plain instruction. Instead of being appended to a conversation,
1658
+ * Stina runs a hidden deliberation over it and decides herself whether to notify
1659
+ * the user (by starting a new conversation via the `core_init_chat_session` tool).
1660
+ */
1661
+ deliberate?: boolean;
1662
+ /**
1663
+ * Identifier of the source that reported the event (typically the extension id).
1664
+ * Used for logging/tracing of proactive decisions.
1665
+ */
1666
+ source?: string;
1667
+ /**
1668
+ * Optional visual presentation for the conversation Stina may start from this
1669
+ * event. Only meaningful together with `deliberate: true`; ignored if Stina
1670
+ * decides not to notify the user.
1671
+ */
1672
+ presentation?: ConversationPresentation;
1405
1673
  }
1406
1674
  /**
1407
1675
  * Chat API for appending instructions
@@ -1434,11 +1702,14 @@ interface ExtensionModule {
1434
1702
  /**
1435
1703
  * Restart policy for background tasks.
1436
1704
  * Controls how tasks are restarted after failures.
1705
+ *
1706
+ * A task that returns from its callback without having been aborted is
1707
+ * considered finished on purpose and is never restarted, regardless of policy.
1437
1708
  */
1438
1709
  interface BackgroundRestartPolicy {
1439
1710
  /**
1440
1711
  * When to restart the task:
1441
- * - 'always': Always restart, regardless of exit reason
1712
+ * - 'always': Restart if the task threw, or stopped without anyone asking it to
1442
1713
  * - 'on-failure': Only restart if the task threw an error
1443
1714
  * - 'never': Never restart automatically
1444
1715
  */
@@ -1519,6 +1790,11 @@ interface BackgroundTaskContext extends ExecutionContext {
1519
1790
  * Callback function for background tasks.
1520
1791
  * The function should run until the signal is aborted, then clean up and return.
1521
1792
  *
1793
+ * Returning before the signal is aborted means the task has finished on purpose:
1794
+ * it is marked as completed and is not restarted, whatever the restart policy says.
1795
+ * A task that has nothing to do right now but wants to be retried later should
1796
+ * therefore keep running (and wait on the signal) rather than return early.
1797
+ *
1522
1798
  * @example
1523
1799
  * ```typescript
1524
1800
  * const callback: BackgroundTaskCallback = async (ctx) => {
@@ -1554,8 +1830,10 @@ interface BackgroundTaskHealth {
1554
1830
  userId: string;
1555
1831
  /**
1556
1832
  * Current task status.
1833
+ * 'completed' means the task returned on its own and will not be restarted,
1834
+ * while 'stopped' means someone asked it to stop.
1557
1835
  */
1558
- status: 'pending' | 'running' | 'stopped' | 'failed' | 'restarting';
1836
+ status: 'pending' | 'running' | 'stopped' | 'completed' | 'failed' | 'restarting';
1559
1837
  /**
1560
1838
  * Number of times the task has been restarted.
1561
1839
  */
@@ -1681,4 +1959,4 @@ interface ActionResult {
1681
1959
  error?: string;
1682
1960
  }
1683
1961
 
1684
- export { type StorageAPI as $, type ActionResult as A, type SchedulerAPI as B, type ChatMessage as C, type Disposable as D, type ExtensionContributions as E, type SchedulerJobRequest as F, type GetModelsOptions as G, type SchedulerSchedule as H, type UserProfile as I, type ChatAPI as J, type ChatInstructionMessage as K, type LocalizedString as L, type ModelInfo as M, type NetworkAPI as N, type LogAPI as O, type PanelDefinition as P, type BackgroundWorkersAPI as Q, type BackgroundTaskConfig as R, type SchedulerFirePayload as S, type ToolResult as T, type UserAPI as U, type BackgroundTaskCallback as V, type BackgroundTaskContext as W, type BackgroundTaskHealth as X, type BackgroundRestartPolicy as Y, type Query as Z, type QueryOptions as _, type ChatOptions as a, type SecretsAPI as a0, type StorageCollectionConfig as a1, type StorageContributions as a2, type AIProvider as a3, type ToolCall as a4, type Tool as a5, type Action as a6, type ExtensionModule as a7, type HugeIconName as a8, type AllowedCSSProperty as a9, type PanelAction as aA, type PanelProps as aB, type ToggleProps as aC, type CollapsibleProps as aD, type FrameVariant as aE, type FrameProps as aF, type ListProps as aG, type PillVariant as aH, type PillProps as aI, type CheckboxProps as aJ, type MarkdownProps as aK, type TextPreviewProps as aL, type ModalProps as aM, type ConditionalGroupProps as aN, type ExecutionContext as aO, type ExtensionComponentStyle as aa, type ExtensionComponentData as ab, type ExtensionComponentIterator as ac, type ExtensionComponentChildren as ad, type ExtensionActionCall as ae, type ExtensionActionRef as af, type ExtensionDataSource as ag, type ExtensionPanelDefinition as ah, type HeaderProps as ai, type LabelProps as aj, type ParagraphProps as ak, type ButtonProps as al, type TextInputProps as am, type PasswordInputProps as an, type NumberInputProps as ao, type TextAreaProps as ap, type DateTimeInputProps as aq, type SelectProps as ar, type IconPickerProps as as, type VerticalStackProps as at, type HorizontalStackProps as au, type GridProps as av, type DividerProps as aw, type IconProps as ax, type IconButtonType as ay, type IconButtonProps as az, type StreamEvent as b, type ToolSettingsViewDefinition as c, type ToolSettingsView as d, type ToolSettingsListView as e, type ToolSettingsListMapping as f, type ToolSettingsComponentView as g, type ToolSettingsActionDataSource as h, type PanelView as i, type PanelComponentView as j, type PanelActionDataSource as k, type PanelUnknownView as l, type ProviderDefinition as m, type ProviderConfigView as n, type PromptContribution as o, type PromptSection as p, type ToolDefinition as q, resolveLocalizedString as r, type ToolConfirmationConfig as s, type CommandDefinition as t, type ExtensionContext as u, type SettingsAPI as v, type ProvidersAPI as w, type ToolsAPI as x, type ActionsAPI as y, type EventsAPI as z };
1962
+ export { type BackgroundTaskHealth as $, type ActionResult as A, type EventsAPI as B, type ChatMessage as C, type Disposable as D, type ExtensionContributions as E, type SchedulerAPI as F, type GetModelsOptions as G, type HugeIconName as H, type SchedulerJobRequest as I, type SchedulerSchedule as J, type UserProfile as K, type LocalizedString as L, type ModelInfo as M, type NetworkAPI as N, type ChatAPI as O, type PanelDefinition as P, type ChatInstructionMessage as Q, type ConversationPresentation as R, type SchedulerFirePayload as S, type ToolResult as T, type UserAPI as U, type VoiceSessionOptions as V, type LogAPI as W, type BackgroundWorkersAPI as X, type BackgroundTaskConfig as Y, type BackgroundTaskCallback as Z, type BackgroundTaskContext as _, type ChatOptions as a, type BackgroundRestartPolicy as a0, type Query as a1, type QueryOptions as a2, type StorageAPI as a3, type SecretsAPI as a4, type StorageCollectionConfig as a5, type StorageContributions as a6, type AIProvider as a7, type ModelCapabilities as a8, type ChatImage as a9, type HorizontalStackProps as aA, type GridProps as aB, type DividerProps as aC, type IconProps as aD, type IconButtonType as aE, type IconButtonProps as aF, type PanelAction as aG, type PanelProps as aH, type ToggleProps as aI, type CollapsibleProps as aJ, type FrameVariant as aK, type FrameProps as aL, type ListProps as aM, type PillVariant as aN, type PillProps as aO, type CheckboxProps as aP, type MarkdownProps as aQ, type TextPreviewProps as aR, type ModalProps as aS, type ConditionalGroupProps as aT, type ExecutionContext as aU, type ToolCall as aa, type VoiceTransportRequest as ab, type Tool as ac, type Action as ad, type ExtensionModule as ae, type AllowedCSSProperty as af, type ExtensionComponentStyle as ag, type ExtensionComponentData as ah, type ExtensionComponentIterator as ai, type ExtensionComponentChildren as aj, type ExtensionActionCall as ak, type ExtensionActionRef as al, type ExtensionDataSource as am, type ExtensionPanelDefinition as an, type HeaderProps as ao, type LabelProps as ap, type ParagraphProps as aq, type ButtonProps as ar, type TextInputProps as as, type PasswordInputProps as at, type NumberInputProps as au, type TextAreaProps as av, type DateTimeInputProps as aw, type SelectProps as ax, type IconPickerProps as ay, type VerticalStackProps as az, type StreamEvent as b, type VoiceSessionDescriptor as c, type ToolSettingsViewDefinition as d, type ToolSettingsView as e, type ToolSettingsListView as f, type ToolSettingsListMapping as g, type ToolSettingsComponentView as h, type ToolSettingsActionDataSource as i, type PanelView as j, type PanelComponentView as k, type PanelActionDataSource as l, type PanelUnknownView as m, type ProviderDefinition as n, type ProviderConfigView as o, type PromptContribution as p, type PromptSection as q, resolveLocalizedString as r, type ToolDefinition as s, type ToolConfirmationConfig as t, type CommandDefinition as u, type ExtensionContext as v, type SettingsAPI as w, type ProvidersAPI as x, type ToolsAPI as y, type ActionsAPI as z };