@sentry/junior-plugin-api 0.124.2 → 0.126.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
@@ -44,9 +44,17 @@ reports, and other typed hook surfaces exported by this package.
44
44
  database, logging, and only the host capabilities required by that hook.
45
45
  - Prompt hooks return bounded structured prompt messages rather than mutate the
46
46
  core prompt.
47
+ - User prompt hooks for durable turns may emit registered structured events
48
+ through `ctx.events` for auxiliary work completed while building context.
47
49
  - Tool hooks return model-visible schemas aligned with their executor inputs.
48
50
  - Host-owned structured model and embedding calls do not expose provider
49
- credentials to plugins.
51
+ credentials to plugins. Both return a best-effort provider cost estimate when
52
+ one is available.
53
+ - Operational report and authenticated API hooks may aggregate `costUsd` from
54
+ their own registered conversation events through `ctx.eventStats`. Core
55
+ binds the plugin namespace and owns access to the conversation event log.
56
+ Event `costUsd` is additive operation cost and must not duplicate cost
57
+ already recorded in the conversation's agent model usage.
50
58
  - Authenticated API route apps receive one verified viewer in their request
51
59
  context. The registration hook exposes actor resolution for plugins whose
52
60
  viewer-owned data spans platform identities.
@@ -76,10 +84,14 @@ routing, response validation, rendering, confirmation, and query state.
76
84
  - Conversation-bound background tasks may emit registered structured events
77
85
  through `ctx.events`. Define each version with `defineConversationEvent()`;
78
86
  the host supplies the plugin namespace, conversation, turn, ordering, and
79
- timestamps. Event definitions return bounded transcript presentation data,
80
- while Junior owns browser rendering.
87
+ timestamps without treating background work as new conversation activity.
88
+ Event definitions return bounded transcript presentation data, while Junior
89
+ owns browser rendering.
81
90
  - `ctx.agent.dispatch` creates durable agent work with an explicit actor,
82
91
  destination, source, metadata, and idempotency identity.
92
+ - Dispatches may include compact `replyAttribution` for destination-visible
93
+ context about what produced the reply. Core owns platform rendering; opaque
94
+ dispatch metadata remains internal.
83
95
  - Delegated credential subjects declare the narrow action that authorized them.
84
96
  Core owns runtime bindings; scheduler task subjects are accepted only from the
85
97
  scheduler plugin and are bound to the exact task id.
@@ -90,11 +102,14 @@ routing, response validation, rendering, confirmation, and query state.
90
102
 
91
103
  Register plugin-owned event definitions through `conversationEvents`. A
92
104
  definition owns one local name, version, content schema, and `renderEvent()`
93
- projection. The active plugin context supplies the namespace, so plugins cannot
94
- emit native events or impersonate another plugin. The `junior` plugin name is
95
- reserved for host-owned native events. Stored events remain durable when a
96
- plugin is removed, but normal transcript projection skips definitions that are
97
- not currently registered.
105
+ projection. A renderer may return `undefined` when an event should remain
106
+ durable without producing a transcript row. The active plugin context supplies
107
+ the namespace, so plugins cannot emit native events or impersonate another
108
+ plugin. The `junior` plugin name is reserved for host-owned native events.
109
+ Versions of the same event name share one operation idempotency identity,
110
+ so keep previous definitions registered when evolving an event. Stored events
111
+ remain durable when a plugin is removed, but normal transcript projection skips
112
+ definitions that are not currently registered.
98
113
 
99
114
  ## Database
100
115
 
package/dist/context.d.ts CHANGED
@@ -14,7 +14,7 @@ export type SlackSource = Extract<Source, {
14
14
  export type LocalSource = Extract<Source, {
15
15
  platform: "local";
16
16
  }>;
17
- export type SourceType = Source["type"];
17
+ export type SourceVisibility = Source["visibility"];
18
18
  export type Destination = z.output<typeof destinationSchema>;
19
19
  export type SlackDestination = Extract<Destination, {
20
20
  platform: "slack";
@@ -38,6 +38,8 @@ export interface PluginModel {
38
38
  schema: TSchema;
39
39
  system?: string;
40
40
  }): Promise<{
41
+ /** Best-effort estimated provider cost for this completion. */
42
+ costUsd?: number;
41
43
  object: z.infer<TSchema>;
42
44
  }>;
43
45
  }
@@ -46,6 +48,8 @@ export interface PluginEmbedder {
46
48
  embedTexts(input: {
47
49
  texts: string[];
48
50
  }): Promise<{
51
+ /** Best-effort estimated provider cost for this embedding call. */
52
+ costUsd?: number;
49
53
  dimensions: number;
50
54
  model: string;
51
55
  provider: string;
@@ -87,7 +91,7 @@ export declare function createSlackSource(input: {
87
91
  teamId: string;
88
92
  threadTs?: string;
89
93
  /** Runtime-normalized source visibility. */
90
- type: SourceType;
94
+ visibility: SourceVisibility;
91
95
  }): SlackSource;
92
96
  /** Build a normalized local source from a local conversation id. */
93
97
  export declare function createLocalSource(conversationId: string): LocalSource;
@@ -39,12 +39,12 @@ export interface PluginConversationEventValue {
39
39
  readonly data: Record<string, unknown>;
40
40
  readonly definition: PluginConversationEventDefinition;
41
41
  }
42
- /** Registered schema and transcript presentation for one plugin event version. */
42
+ /** Registered schema and optional transcript presentation for one event version. */
43
43
  export interface PluginConversationEventDefinition {
44
44
  readonly eventName: string;
45
45
  readonly version: number;
46
46
  parse(data: unknown): Record<string, unknown>;
47
- renderEvent(data: Record<string, unknown>): ConversationEventPresentation;
47
+ renderEvent(data: Record<string, unknown>): ConversationEventPresentation | undefined;
48
48
  }
49
49
  /** Typed factory returned while authoring one plugin conversation event. */
50
50
  export interface DefinedConversationEvent<TInput> extends PluginConversationEventDefinition {
@@ -55,9 +55,21 @@ export declare function defineConversationEvent<TSchema extends z.ZodType<Record
55
55
  name: string;
56
56
  version: number;
57
57
  schema: TSchema;
58
- renderEvent(event: z.output<TSchema>): z.input<typeof conversationEventPresentationSchema>;
58
+ renderEvent(event: z.output<TSchema>): z.input<typeof conversationEventPresentationSchema> | undefined;
59
59
  }): DefinedConversationEvent<z.input<TSchema>>;
60
60
  /** Conversation-bound event writer supplied by Junior core. */
61
61
  export interface PluginConversationEvents {
62
62
  emit(event: PluginConversationEventValue): Promise<void>;
63
63
  }
64
+ export interface PluginConversationEventCostDay {
65
+ costUsd: number;
66
+ date: string;
67
+ events: number;
68
+ }
69
+ /** Read aggregate costs for events owned by the current plugin namespace. */
70
+ export interface PluginConversationEventStats {
71
+ costsByDay(input: {
72
+ days: 7 | 30 | 90;
73
+ eventName: string;
74
+ }): Promise<PluginConversationEventCostDay[]>;
75
+ }
@@ -1,7 +1,9 @@
1
1
  import { z } from "zod";
2
- import { destinationVisibilitySchema, dispatchOptionsSchema } from "./schemas";
2
+ import { destinationVisibilitySchema, dispatchOptionsSchema, replyAttributionSchema } from "./schemas";
3
3
  export type DestinationVisibility = z.output<typeof destinationVisibilitySchema>;
4
4
  export type DispatchOptions = z.output<typeof dispatchOptionsSchema>;
5
+ /** Compact destination-visible context explaining what produced a reply. */
6
+ export type ReplyAttribution = z.output<typeof replyAttributionSchema>;
5
7
  export interface DispatchResult {
6
8
  id: string;
7
9
  status: "created" | "already_exists";
package/dist/index.js CHANGED
@@ -64,9 +64,8 @@ function defineConversationEvent(definition) {
64
64
  },
65
65
  renderEvent(data) {
66
66
  const parsed = definition.schema.parse(data);
67
- return conversationEventPresentationSchema.parse(
68
- definition.renderEvent(parsed)
69
- );
67
+ const presentation = definition.renderEvent(parsed);
68
+ return presentation === void 0 ? void 0 : conversationEventPresentationSchema.parse(presentation);
70
69
  }
71
70
  });
72
71
  return eventDefinition;
@@ -85,7 +84,7 @@ var exactNonBlankStringSchema = nonBlankStringSchema.refine(
85
84
  (value) => value === value.trim()
86
85
  );
87
86
  var platformSchema = z3.enum(["slack", "local"]);
88
- var sourceTypeSchema = z3.enum(["pub", "priv"]);
87
+ var sourceVisibilitySchema = z3.enum(["public", "private"]);
89
88
  var destinationVisibilitySchema = z3.enum(["public", "private"]);
90
89
  var slackAddressSchema = z3.object({
91
90
  platform: z3.literal("slack"),
@@ -102,13 +101,13 @@ var destinationSchema = z3.discriminatedUnion("platform", [
102
101
  localDestinationSchema
103
102
  ]);
104
103
  var slackSourceSchema = slackAddressSchema.extend({
105
- type: sourceTypeSchema,
104
+ visibility: sourceVisibilitySchema,
106
105
  messageTs: nonBlankStringSchema.optional(),
107
106
  threadTs: nonBlankStringSchema.optional()
108
107
  }).strict();
109
108
  var localSourceSchema = z3.object({
110
109
  platform: z3.literal("local"),
111
- type: z3.literal("priv"),
110
+ visibility: z3.literal("private"),
112
111
  conversationId: localConversationIdSchema
113
112
  }).strict();
114
113
  var sourceSchema = z3.discriminatedUnion("platform", [
@@ -203,6 +202,10 @@ var dispatchMetadataSchema = z3.record(z3.string(), z3.string()).superRefine((me
203
202
  }
204
203
  }
205
204
  });
205
+ var replyAttributionSchema = z3.object({
206
+ label: exactNonBlankStringSchema.pipe(z3.string().max(48)).refine((value) => !/[\r\n]/.test(value)),
207
+ detail: exactNonBlankStringSchema.pipe(z3.string().max(128)).refine((value) => !/[\r\n]/.test(value)).optional()
208
+ }).strict();
206
209
  var dispatchOptionsSchema = z3.object({
207
210
  idempotencyKey: nonBlankStringSchema.pipe(z3.string().max(512)),
208
211
  credentialSubject: pluginCredentialSubjectSchema.optional(),
@@ -210,6 +213,7 @@ var dispatchOptionsSchema = z3.object({
210
213
  destinationVisibility: destinationVisibilitySchema,
211
214
  input: nonBlankStringSchema.pipe(z3.string().max(32e3)),
212
215
  metadata: dispatchMetadataSchema.optional(),
216
+ replyAttribution: replyAttributionSchema.optional(),
213
217
  source: sourceSchema
214
218
  }).strict();
215
219
 
@@ -217,7 +221,7 @@ var dispatchOptionsSchema = z3.object({
217
221
  function createSlackSource(input) {
218
222
  return {
219
223
  platform: "slack",
220
- type: input.type,
224
+ visibility: input.visibility,
221
225
  teamId: input.teamId,
222
226
  channelId: input.channelId,
223
227
  ...input.messageTs ? { messageTs: input.messageTs } : {},
@@ -227,12 +231,12 @@ function createSlackSource(input) {
227
231
  function createLocalSource(conversationId) {
228
232
  return {
229
233
  platform: "local",
230
- type: "priv",
234
+ visibility: "private",
231
235
  conversationId
232
236
  };
233
237
  }
234
238
  function isPrivateSource(source) {
235
- return source.type === "priv";
239
+ return source.visibility === "private";
236
240
  }
237
241
  function getSourceKey(source) {
238
242
  if (source.platform === "local") {
@@ -747,13 +751,14 @@ export {
747
751
  pluginUserPageLinksSchema,
748
752
  promptContextSchema,
749
753
  promptMessageSchema,
754
+ replyAttributionSchema,
750
755
  resourceEventSchema,
751
756
  resourceLinkAnnotationSchema,
752
757
  slackActorSchema,
753
758
  slackDestinationSchema,
754
759
  slackSourceSchema,
755
760
  sourceSchema,
756
- sourceTypeSchema,
761
+ sourceVisibilitySchema,
757
762
  subscribableResourceSchema,
758
763
  systemActorSchema,
759
764
  toolApprovalModeSchema,
@@ -4,6 +4,7 @@ import type { Dispatch, DispatchOptions, DispatchResult } from "./dispatch";
4
4
  import type { PluginReadState, PluginState } from "./state";
5
5
  import type { ResourceEventPublisher } from "./resource-events";
6
6
  import type { PluginConversationAnnotations } from "./annotations";
7
+ import type { PluginConversationEventStats } from "./conversation-events";
7
8
  export interface HeartbeatHookContext extends PluginContext {
8
9
  agent: {
9
10
  dispatch(options: DispatchOptions): Promise<DispatchResult>;
@@ -37,6 +38,7 @@ export interface PluginOperationalRecordSet {
37
38
  title: string;
38
39
  }
39
40
  export interface PluginOperationalChartSeries {
41
+ format?: "usd";
40
42
  key: string;
41
43
  label: string;
42
44
  tone?: PluginOperationalTone;
@@ -67,6 +69,7 @@ export interface PluginOperationalReport extends PluginOperationalReportContent
67
69
  pluginName: string;
68
70
  }
69
71
  export interface OperationalReportHookContext extends PluginContext {
72
+ eventStats: PluginConversationEventStats;
70
73
  nowMs: number;
71
74
  state: PluginReadState;
72
75
  }
@@ -89,6 +92,7 @@ export interface RouteRegistrationHookContext extends PluginContext {
89
92
  resourceEvents: ResourceEventPublisher;
90
93
  }
91
94
  export interface ApiRouteRegistrationHookContext extends PluginContext {
95
+ eventStats: PluginConversationEventStats;
92
96
  viewer: {
93
97
  /** Resolve every runtime actor linked to one authenticated viewer email. */
94
98
  actors(email: string): Promise<Array<LocalActor | SlackActor>>;
package/dist/prompt.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import type { Destination, Platform, PluginContext, PluginEmbedder, PluginModel, Actor, Source } from "./context";
3
3
  import type { PluginState } from "./state";
4
+ import type { PluginConversationEvents } from "./conversation-events";
4
5
  export declare const promptMessageSchema: z.ZodObject<{
5
6
  text: z.ZodString;
6
7
  }, z.core.$strict>;
@@ -36,6 +37,8 @@ export type UserPromptContext = Pick<PluginContext, "db" | "log" | "plugin"> & {
36
37
  conversationId?: string;
37
38
  destination: Destination;
38
39
  embedder: PluginEmbedder;
40
+ /** Conversation-bound event writer when the prompt belongs to a durable turn. */
41
+ events?: PluginConversationEvents;
39
42
  model: PluginModel;
40
43
  actor?: Actor;
41
44
  source: Source;
package/dist/schemas.d.ts CHANGED
@@ -6,9 +6,9 @@ export declare const platformSchema: z.ZodEnum<{
6
6
  local: "local";
7
7
  }>;
8
8
  /** Runtime source visibility visible to plugins. */
9
- export declare const sourceTypeSchema: z.ZodEnum<{
10
- pub: "pub";
11
- priv: "priv";
9
+ export declare const sourceVisibilitySchema: z.ZodEnum<{
10
+ public: "public";
11
+ private: "private";
12
12
  }>;
13
13
  /** Provider-neutral visibility of a routed destination. */
14
14
  export declare const destinationVisibilitySchema: z.ZodEnum<{
@@ -40,9 +40,9 @@ export declare const slackSourceSchema: z.ZodObject<{
40
40
  platform: z.ZodLiteral<"slack">;
41
41
  teamId: z.ZodString;
42
42
  channelId: z.ZodString;
43
- type: z.ZodEnum<{
44
- pub: "pub";
45
- priv: "priv";
43
+ visibility: z.ZodEnum<{
44
+ public: "public";
45
+ private: "private";
46
46
  }>;
47
47
  messageTs: z.ZodOptional<z.ZodString>;
48
48
  threadTs: z.ZodOptional<z.ZodString>;
@@ -50,7 +50,7 @@ export declare const slackSourceSchema: z.ZodObject<{
50
50
  /** Runtime-owned local CLI coordinates for the inbound invocation. */
51
51
  export declare const localSourceSchema: z.ZodObject<{
52
52
  platform: z.ZodLiteral<"local">;
53
- type: z.ZodLiteral<"priv">;
53
+ visibility: z.ZodLiteral<"private">;
54
54
  conversationId: z.ZodString;
55
55
  }, z.core.$strict>;
56
56
  /** Runtime-owned provider-neutral coordinates for the inbound invocation. */
@@ -58,15 +58,15 @@ export declare const sourceSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
58
58
  platform: z.ZodLiteral<"slack">;
59
59
  teamId: z.ZodString;
60
60
  channelId: z.ZodString;
61
- type: z.ZodEnum<{
62
- pub: "pub";
63
- priv: "priv";
61
+ visibility: z.ZodEnum<{
62
+ public: "public";
63
+ private: "private";
64
64
  }>;
65
65
  messageTs: z.ZodOptional<z.ZodString>;
66
66
  threadTs: z.ZodOptional<z.ZodString>;
67
67
  }, z.core.$strict>, z.ZodObject<{
68
68
  platform: z.ZodLiteral<"local">;
69
- type: z.ZodLiteral<"priv">;
69
+ visibility: z.ZodLiteral<"private">;
70
70
  conversationId: z.ZodString;
71
71
  }, z.core.$strict>], "platform">;
72
72
  /** Stable user credential subject shape accepted from plugins. */
@@ -117,6 +117,11 @@ export declare const actorSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
117
117
  platform: z.ZodLiteral<"system">;
118
118
  name: z.ZodString;
119
119
  }, z.core.$strict>], "platform">;
120
+ /** Compact destination-visible context explaining what produced a reply. */
121
+ export declare const replyAttributionSchema: z.ZodObject<{
122
+ label: z.ZodPipe<z.ZodString, z.ZodString>;
123
+ detail: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodString>>;
124
+ }, z.core.$strict>;
120
125
  /** Plugin dispatch request accepted by Junior core. */
121
126
  export declare const dispatchOptionsSchema: z.ZodObject<{
122
127
  idempotencyKey: z.ZodPipe<z.ZodString, z.ZodString>;
@@ -141,19 +146,23 @@ export declare const dispatchOptionsSchema: z.ZodObject<{
141
146
  }>;
142
147
  input: z.ZodPipe<z.ZodString, z.ZodString>;
143
148
  metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
149
+ replyAttribution: z.ZodOptional<z.ZodObject<{
150
+ label: z.ZodPipe<z.ZodString, z.ZodString>;
151
+ detail: z.ZodOptional<z.ZodPipe<z.ZodString, z.ZodString>>;
152
+ }, z.core.$strict>>;
144
153
  source: z.ZodDiscriminatedUnion<[z.ZodObject<{
145
154
  platform: z.ZodLiteral<"slack">;
146
155
  teamId: z.ZodString;
147
156
  channelId: z.ZodString;
148
- type: z.ZodEnum<{
149
- pub: "pub";
150
- priv: "priv";
157
+ visibility: z.ZodEnum<{
158
+ public: "public";
159
+ private: "private";
151
160
  }>;
152
161
  messageTs: z.ZodOptional<z.ZodString>;
153
162
  threadTs: z.ZodOptional<z.ZodString>;
154
163
  }, z.core.$strict>, z.ZodObject<{
155
164
  platform: z.ZodLiteral<"local">;
156
- type: z.ZodLiteral<"priv">;
165
+ visibility: z.ZodLiteral<"private">;
157
166
  conversationId: z.ZodString;
158
167
  }, z.core.$strict>], "platform">;
159
168
  }, z.core.$strict>;
package/dist/tasks.d.ts CHANGED
@@ -126,15 +126,15 @@ export declare const pluginRunContextSchema: z.ZodObject<{
126
126
  platform: z.ZodLiteral<"slack">;
127
127
  teamId: z.ZodString;
128
128
  channelId: z.ZodString;
129
- type: z.ZodEnum<{
130
- pub: "pub";
131
- priv: "priv";
129
+ visibility: z.ZodEnum<{
130
+ public: "public";
131
+ private: "private";
132
132
  }>;
133
133
  messageTs: z.ZodOptional<z.ZodString>;
134
134
  threadTs: z.ZodOptional<z.ZodString>;
135
135
  }, z.core.$strict>, z.ZodObject<{
136
136
  platform: z.ZodLiteral<"local">;
137
- type: z.ZodLiteral<"priv">;
137
+ visibility: z.ZodLiteral<"private">;
138
138
  conversationId: z.ZodString;
139
139
  }, z.core.$strict>], "platform">;
140
140
  transcript: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/junior-plugin-api",
3
- "version": "0.124.2",
3
+ "version": "0.126.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
package/src/context.ts CHANGED
@@ -19,7 +19,7 @@ export type SystemActor = z.output<typeof systemActorSchema>;
19
19
  export type Source = z.output<typeof sourceSchema>;
20
20
  export type SlackSource = Extract<Source, { platform: "slack" }>;
21
21
  export type LocalSource = Extract<Source, { platform: "local" }>;
22
- export type SourceType = Source["type"];
22
+ export type SourceVisibility = Source["visibility"];
23
23
 
24
24
  export type Destination = z.output<typeof destinationSchema>;
25
25
 
@@ -44,12 +44,18 @@ export interface PluginModel {
44
44
  prompt: string;
45
45
  schema: TSchema;
46
46
  system?: string;
47
- }): Promise<{ object: z.infer<TSchema> }>;
47
+ }): Promise<{
48
+ /** Best-effort estimated provider cost for this completion. */
49
+ costUsd?: number;
50
+ object: z.infer<TSchema>;
51
+ }>;
48
52
  }
49
53
 
50
54
  export interface PluginEmbedder {
51
55
  /** Embed plugin-owned text for derived retrieval without exposing provider credentials. */
52
56
  embedTexts(input: { texts: string[] }): Promise<{
57
+ /** Best-effort estimated provider cost for this embedding call. */
58
+ costUsd?: number;
53
59
  dimensions: number;
54
60
  model: string;
55
61
  provider: string;
@@ -97,11 +103,11 @@ export function createSlackSource(input: {
97
103
  teamId: string;
98
104
  threadTs?: string;
99
105
  /** Runtime-normalized source visibility. */
100
- type: SourceType;
106
+ visibility: SourceVisibility;
101
107
  }): SlackSource {
102
108
  return {
103
109
  platform: "slack",
104
- type: input.type,
110
+ visibility: input.visibility,
105
111
  teamId: input.teamId,
106
112
  channelId: input.channelId,
107
113
  ...(input.messageTs ? { messageTs: input.messageTs } : {}),
@@ -113,14 +119,14 @@ export function createSlackSource(input: {
113
119
  export function createLocalSource(conversationId: string): LocalSource {
114
120
  return {
115
121
  platform: "local",
116
- type: "priv",
122
+ visibility: "private",
117
123
  conversationId,
118
124
  };
119
125
  }
120
126
 
121
127
  /** Return whether a source is private to a person or restricted group. */
122
128
  export function isPrivateSource(source: Source): boolean {
123
- return source.type === "priv";
129
+ return source.visibility === "private";
124
130
  }
125
131
 
126
132
  /** Return the stable source identity used for idempotency and attribution. */
@@ -48,12 +48,14 @@ export interface PluginConversationEventValue {
48
48
  readonly definition: PluginConversationEventDefinition;
49
49
  }
50
50
 
51
- /** Registered schema and transcript presentation for one plugin event version. */
51
+ /** Registered schema and optional transcript presentation for one event version. */
52
52
  export interface PluginConversationEventDefinition {
53
53
  readonly eventName: string;
54
54
  readonly version: number;
55
55
  parse(data: unknown): Record<string, unknown>;
56
- renderEvent(data: Record<string, unknown>): ConversationEventPresentation;
56
+ renderEvent(
57
+ data: Record<string, unknown>,
58
+ ): ConversationEventPresentation | undefined;
57
59
  }
58
60
 
59
61
  /** Typed factory returned while authoring one plugin conversation event. */
@@ -72,7 +74,7 @@ export function defineConversationEvent<
72
74
  schema: TSchema;
73
75
  renderEvent(
74
76
  event: z.output<TSchema>,
75
- ): z.input<typeof conversationEventPresentationSchema>;
77
+ ): z.input<typeof conversationEventPresentationSchema> | undefined;
76
78
  }): DefinedConversationEvent<z.input<TSchema>> {
77
79
  const identity = z
78
80
  .object({
@@ -96,9 +98,10 @@ export function defineConversationEvent<
96
98
  },
97
99
  renderEvent(data: Record<string, unknown>) {
98
100
  const parsed = definition.schema.parse(data);
99
- return conversationEventPresentationSchema.parse(
100
- definition.renderEvent(parsed),
101
- );
101
+ const presentation = definition.renderEvent(parsed);
102
+ return presentation === undefined
103
+ ? undefined
104
+ : conversationEventPresentationSchema.parse(presentation);
102
105
  },
103
106
  });
104
107
  return eventDefinition;
@@ -108,3 +111,17 @@ export function defineConversationEvent<
108
111
  export interface PluginConversationEvents {
109
112
  emit(event: PluginConversationEventValue): Promise<void>;
110
113
  }
114
+
115
+ export interface PluginConversationEventCostDay {
116
+ costUsd: number;
117
+ date: string;
118
+ events: number;
119
+ }
120
+
121
+ /** Read aggregate costs for events owned by the current plugin namespace. */
122
+ export interface PluginConversationEventStats {
123
+ costsByDay(input: {
124
+ days: 7 | 30 | 90;
125
+ eventName: string;
126
+ }): Promise<PluginConversationEventCostDay[]>;
127
+ }
package/src/dispatch.ts CHANGED
@@ -2,12 +2,15 @@ import { z } from "zod";
2
2
  import {
3
3
  destinationVisibilitySchema,
4
4
  dispatchOptionsSchema,
5
+ replyAttributionSchema,
5
6
  } from "./schemas";
6
7
 
7
8
  export type DestinationVisibility = z.output<
8
9
  typeof destinationVisibilitySchema
9
10
  >;
10
11
  export type DispatchOptions = z.output<typeof dispatchOptionsSchema>;
12
+ /** Compact destination-visible context explaining what produced a reply. */
13
+ export type ReplyAttribution = z.output<typeof replyAttributionSchema>;
11
14
 
12
15
  export interface DispatchResult {
13
16
  id: string;
package/src/operations.ts CHANGED
@@ -5,6 +5,7 @@ import { nonBlankStringSchema } from "./schemas";
5
5
  import type { PluginReadState, PluginState } from "./state";
6
6
  import type { ResourceEventPublisher } from "./resource-events";
7
7
  import type { PluginConversationAnnotations } from "./annotations";
8
+ import type { PluginConversationEventStats } from "./conversation-events";
8
9
 
9
10
  export interface HeartbeatHookContext extends PluginContext {
10
11
  agent: {
@@ -46,6 +47,7 @@ export interface PluginOperationalRecordSet {
46
47
  }
47
48
 
48
49
  export interface PluginOperationalChartSeries {
50
+ format?: "usd";
49
51
  key: string;
50
52
  label: string;
51
53
  tone?: PluginOperationalTone;
@@ -81,6 +83,7 @@ export interface PluginOperationalReport extends PluginOperationalReportContent
81
83
  }
82
84
 
83
85
  export interface OperationalReportHookContext extends PluginContext {
86
+ eventStats: PluginConversationEventStats;
84
87
  nowMs: number;
85
88
  state: PluginReadState;
86
89
  }
@@ -120,6 +123,7 @@ export interface RouteRegistrationHookContext extends PluginContext {
120
123
  }
121
124
 
122
125
  export interface ApiRouteRegistrationHookContext extends PluginContext {
126
+ eventStats: PluginConversationEventStats;
123
127
  viewer: {
124
128
  /** Resolve every runtime actor linked to one authenticated viewer email. */
125
129
  actors(email: string): Promise<Array<LocalActor | SlackActor>>;
package/src/prompt.ts CHANGED
@@ -9,6 +9,7 @@ import type {
9
9
  Source,
10
10
  } from "./context";
11
11
  import type { PluginState } from "./state";
12
+ import type { PluginConversationEvents } from "./conversation-events";
12
13
 
13
14
  const promptContextKindSchema = z
14
15
  .string()
@@ -80,6 +81,8 @@ export type UserPromptContext = Pick<PluginContext, "db" | "log" | "plugin"> & {
80
81
  conversationId?: string;
81
82
  destination: Destination;
82
83
  embedder: PluginEmbedder;
84
+ /** Conversation-bound event writer when the prompt belongs to a durable turn. */
85
+ events?: PluginConversationEvents;
83
86
  model: PluginModel;
84
87
  actor?: Actor;
85
88
  source: Source;
package/src/schemas.ts CHANGED
@@ -23,7 +23,7 @@ const exactNonBlankStringSchema = nonBlankStringSchema.refine(
23
23
  export const platformSchema = z.enum(["slack", "local"]);
24
24
 
25
25
  /** Runtime source visibility visible to plugins. */
26
- export const sourceTypeSchema = z.enum(["pub", "priv"]);
26
+ export const sourceVisibilitySchema = z.enum(["public", "private"]);
27
27
 
28
28
  /** Provider-neutral visibility of a routed destination. */
29
29
  export const destinationVisibilitySchema = z.enum(["public", "private"]);
@@ -56,7 +56,7 @@ export const destinationSchema = z.discriminatedUnion("platform", [
56
56
  /** Runtime-owned Slack coordinates for the inbound invocation. */
57
57
  export const slackSourceSchema = slackAddressSchema
58
58
  .extend({
59
- type: sourceTypeSchema,
59
+ visibility: sourceVisibilitySchema,
60
60
  messageTs: nonBlankStringSchema.optional(),
61
61
  threadTs: nonBlankStringSchema.optional(),
62
62
  })
@@ -66,7 +66,7 @@ export const slackSourceSchema = slackAddressSchema
66
66
  export const localSourceSchema = z
67
67
  .object({
68
68
  platform: z.literal("local"),
69
- type: z.literal("priv"),
69
+ visibility: z.literal("private"),
70
70
  conversationId: localConversationIdSchema,
71
71
  })
72
72
  .strict();
@@ -187,6 +187,19 @@ const dispatchMetadataSchema = z
187
187
  }
188
188
  });
189
189
 
190
+ /** Compact destination-visible context explaining what produced a reply. */
191
+ export const replyAttributionSchema = z
192
+ .object({
193
+ label: exactNonBlankStringSchema
194
+ .pipe(z.string().max(48))
195
+ .refine((value) => !/[\r\n]/.test(value)),
196
+ detail: exactNonBlankStringSchema
197
+ .pipe(z.string().max(128))
198
+ .refine((value) => !/[\r\n]/.test(value))
199
+ .optional(),
200
+ })
201
+ .strict();
202
+
190
203
  /** Plugin dispatch request accepted by Junior core. */
191
204
  export const dispatchOptionsSchema = z
192
205
  .object({
@@ -196,6 +209,7 @@ export const dispatchOptionsSchema = z
196
209
  destinationVisibility: destinationVisibilitySchema,
197
210
  input: nonBlankStringSchema.pipe(z.string().max(32_000)),
198
211
  metadata: dispatchMetadataSchema.optional(),
212
+ replyAttribution: replyAttributionSchema.optional(),
199
213
  source: sourceSchema,
200
214
  })
201
215
  .strict();