@canonmsg/agent-sdk 1.3.0 → 1.4.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.
@@ -1,5 +1,5 @@
1
- import { type AddMemberResult, type CanonContact, type ContactCardPayload, type CreateContactRequestResult } from '@canonmsg/core';
2
- import type { CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, CreateConversationOptions, MessageHandler, ReachOutOptions, ReachOutResult, ContactRequestHandler, RuntimeSignalHandler } from './types.js';
1
+ import { type AddMemberResult, type CanonContact, type CanonRuntimeActivityItem, type CanonRuntimeCommandDescriptor, type CanonRuntimeFact, type CanonRuntimePrimitiveId, type ContactCardPayload, type ClearRuntimeActivityOptions, type CreateContactRequestResult } from '@canonmsg/core';
2
+ import type { CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, CreateConversationOptions, MessageHandler, ReachOutOptions, ReachOutResult, ContactRequestHandler, RuntimeSignalHandler, RuntimePrimitiveHandler } from './types.js';
3
3
  /**
4
4
  * Contact-graph operations exposed under `agent.contacts`. Wraps the REST
5
5
  * endpoints in CanonClient — the same surface a human user would hit through
@@ -33,6 +33,8 @@ export declare class CanonAgent {
33
33
  private interruptHandler;
34
34
  private stopAndDropHandler;
35
35
  private newSessionHandler;
36
+ private readonly primitiveHandlers;
37
+ private primitiveFallbackHandler;
36
38
  /** Contact-graph operations (`agent.contacts.*`). Initialized in the constructor. */
37
39
  readonly contacts: AgentContactsAPI;
38
40
  /** Block/unblock operations (`agent.users.*`). Initialized in the constructor. */
@@ -45,6 +47,7 @@ export declare class CanonAgent {
45
47
  private runtimeHeartbeatTimer;
46
48
  private runtimeControlPollTimer;
47
49
  private readonly lastSeenSignal;
50
+ private readonly primitiveRequestDedupe;
48
51
  private readonly activeAbortControllers;
49
52
  private readonly conversationMemberIds;
50
53
  private readonly pendingMembershipChanges;
@@ -57,6 +60,11 @@ export declare class CanonAgent {
57
60
  on(event: 'interrupt', handler: RuntimeSignalHandler): void;
58
61
  on(event: 'stopAndDrop', handler: RuntimeSignalHandler): void;
59
62
  on(event: 'newSession', handler: RuntimeSignalHandler): void;
63
+ onPrimitive(primitive: CanonRuntimePrimitiveId | '*', handler: RuntimePrimitiveHandler): void;
64
+ describeCommands(_provider?: string): ReadonlyArray<CanonRuntimeCommandDescriptor>;
65
+ publishRuntimeFacts(conversationId: string, facts: ReadonlyArray<CanonRuntimeFact>): Promise<void>;
66
+ publishRuntimeActivity(conversationId: string, item: CanonRuntimeActivityItem): Promise<void>;
67
+ clearRuntimeActivity(conversationId: string, options?: ClearRuntimeActivityOptions): Promise<void>;
60
68
  /**
61
69
  * Resolve admission live for a target user (typically read off a shared
62
70
  * contact card) and route into either an immediate message or a contact
@@ -100,6 +108,8 @@ export declare class CanonAgent {
100
108
  private hasStopAndDropSupport;
101
109
  private hasNewSessionSupport;
102
110
  private hasRuntimeSignalSupport;
111
+ private hasRuntimePrimitiveSupport;
112
+ private hasRuntimeControlSupport;
103
113
  private buildRuntimeDescriptor;
104
114
  private buildRuntimeCapabilities;
105
115
  private publishAgentRuntime;
@@ -113,12 +123,16 @@ export declare class CanonAgent {
113
123
  private baselineRuntimeControlSignals;
114
124
  private startRuntimeControlPolling;
115
125
  private stopRuntimeControlPolling;
116
- private pollRuntimeControlSignals;
126
+ private pollRuntimeControls;
127
+ private handleRuntimePrimitiveRequests;
128
+ private clearRuntimePrimitiveRequest;
129
+ private prunePrimitiveRequestDedupe;
117
130
  private handleRuntimeSignal;
118
131
  private abortActiveTurns;
119
132
  private resolveBatchDeliveryIntent;
120
133
  private notifyMessageInterrupt;
121
134
  private createRuntimeStatePublisher;
135
+ private requireRuntimeStatePublisher;
122
136
  private handleMessages;
123
137
  private executeHandler;
124
138
  static register(options: {
@@ -5,6 +5,8 @@ import { Debouncer } from './debouncer.js';
5
5
  import { materializeMessageMedia, sendMediaFileMessage, uploadMediaFile, } from './media.js';
6
6
  import { SessionManager } from './session-manager.js';
7
7
  const AGENT_RUNTIME_HEARTBEAT_MS = 30_000;
8
+ const RUNTIME_PRIMITIVE_DEDUPE_TTL_MS = 5 * 60 * 1000;
9
+ const RUNTIME_PRIMITIVE_DEDUPE_MAX = 1_000;
8
10
  const SDK_RUNTIME_CAPABILITIES = {
9
11
  supportsInterrupt: false,
10
12
  supportsQueue: true,
@@ -18,9 +20,154 @@ const DEFAULT_SDK_RUNTIME_DESCRIPTOR = {
18
20
  supportsInterrupt: false,
19
21
  streamingTextMode: 'snapshot',
20
22
  };
23
+ const STANDARD_PRIMITIVE_COMMANDS = {
24
+ 'runtime.status': {
25
+ id: 'runtime-status',
26
+ label: 'Runtime status',
27
+ description: 'Ask the runtime for its current status.',
28
+ primitive: 'runtime.status',
29
+ aliases: ['status'],
30
+ category: 'runtime',
31
+ placements: ['composer_slash', 'command_palette'],
32
+ availability: ['always'],
33
+ dispatch: { kind: 'primitive', primitive: 'runtime.status' },
34
+ },
35
+ 'runtime.reasoning.set': {
36
+ id: 'thinking-level',
37
+ label: 'Thinking level',
38
+ description: 'Set the runtime reasoning or effort level.',
39
+ primitive: 'runtime.reasoning.set',
40
+ aliases: ['think', 'effort'],
41
+ category: 'runtime',
42
+ placements: ['composer_slash', 'command_palette'],
43
+ availability: ['always'],
44
+ args: [{
45
+ id: 'level',
46
+ label: 'Level',
47
+ kind: 'enum',
48
+ required: true,
49
+ choices: [
50
+ { value: 'low', label: 'Low' },
51
+ { value: 'medium', label: 'Medium' },
52
+ { value: 'high', label: 'High' },
53
+ ],
54
+ }],
55
+ dispatch: { kind: 'primitive', primitive: 'runtime.reasoning.set' },
56
+ },
57
+ 'runtime.verbosity.set': {
58
+ id: 'verbosity',
59
+ label: 'Verbosity',
60
+ description: 'Set runtime verbosity.',
61
+ primitive: 'runtime.verbosity.set',
62
+ aliases: ['verbose'],
63
+ category: 'runtime',
64
+ placements: ['composer_slash', 'command_palette'],
65
+ availability: ['always'],
66
+ args: [{
67
+ id: 'level',
68
+ label: 'Level',
69
+ kind: 'enum',
70
+ required: true,
71
+ choices: [
72
+ { value: 'off', label: 'Off' },
73
+ { value: 'on', label: 'On' },
74
+ { value: 'full', label: 'Full' },
75
+ ],
76
+ }],
77
+ dispatch: { kind: 'primitive', primitive: 'runtime.verbosity.set' },
78
+ },
79
+ 'runtime.usage': {
80
+ id: 'usage',
81
+ label: 'Usage',
82
+ description: 'Show or change runtime usage reporting.',
83
+ primitive: 'runtime.usage',
84
+ aliases: ['usage'],
85
+ category: 'runtime',
86
+ placements: ['composer_slash', 'command_palette'],
87
+ availability: ['always'],
88
+ dispatch: { kind: 'primitive', primitive: 'runtime.usage' },
89
+ },
90
+ 'context.compact': {
91
+ id: 'compact-context',
92
+ label: 'Compact context',
93
+ description: 'Ask the runtime to compact its conversation context.',
94
+ primitive: 'context.compact',
95
+ aliases: ['compact'],
96
+ category: 'session',
97
+ placements: ['composer_slash', 'command_palette'],
98
+ availability: ['always'],
99
+ dispatch: { kind: 'primitive', primitive: 'context.compact' },
100
+ },
101
+ 'session.new': {
102
+ id: 'new-session-primitive',
103
+ label: 'New session',
104
+ description: 'Ask the runtime to start a fresh session.',
105
+ primitive: 'session.new',
106
+ aliases: ['new'],
107
+ category: 'session',
108
+ placements: ['composer_slash', 'command_palette', 'session_strip'],
109
+ availability: ['always'],
110
+ dispatch: { kind: 'primitive', primitive: 'session.new' },
111
+ },
112
+ 'session.reset': {
113
+ id: 'reset-session',
114
+ label: 'Reset session',
115
+ description: 'Ask the runtime to reset the current session.',
116
+ primitive: 'session.reset',
117
+ aliases: ['reset'],
118
+ category: 'session',
119
+ placements: ['composer_slash', 'command_palette'],
120
+ availability: ['always'],
121
+ dispatch: { kind: 'primitive', primitive: 'session.reset' },
122
+ },
123
+ };
21
124
  function sleep(ms) {
22
125
  return new Promise((resolve) => setTimeout(resolve, ms));
23
126
  }
127
+ function isRuntimePrimitiveId(value) {
128
+ return value === 'runtime.status'
129
+ || value === 'runtime.reasoning.set'
130
+ || value === 'runtime.verbosity.set'
131
+ || value === 'runtime.usage'
132
+ || value === 'context.compact'
133
+ || value === 'session.new'
134
+ || value === 'session.reset';
135
+ }
136
+ function normalizePrimitiveArgs(value) {
137
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
138
+ return {};
139
+ }
140
+ const args = {};
141
+ for (const [key, rawValue] of Object.entries(value)) {
142
+ if (!/^[A-Za-z0-9_-]+$/.test(key))
143
+ continue;
144
+ if (typeof rawValue === 'boolean' || typeof rawValue === 'string') {
145
+ args[key] = rawValue;
146
+ }
147
+ }
148
+ return args;
149
+ }
150
+ function normalizeRuntimeFact(fact) {
151
+ const id = fact.id.trim();
152
+ const label = fact.label.trim();
153
+ const value = fact.value.trim();
154
+ if (!id || !label || !value)
155
+ return null;
156
+ return {
157
+ ...fact,
158
+ id,
159
+ label,
160
+ value,
161
+ };
162
+ }
163
+ function normalizeRuntimeActivityItem(item) {
164
+ return {
165
+ ...item,
166
+ id: item.id.trim(),
167
+ title: item.title.trim() || item.kind,
168
+ updatedAt: item.updatedAt || Date.now(),
169
+ };
170
+ }
24
171
  export class CanonAgent {
25
172
  options;
26
173
  apiClient;
@@ -36,6 +183,8 @@ export class CanonAgent {
36
183
  interruptHandler = null;
37
184
  stopAndDropHandler = null;
38
185
  newSessionHandler = null;
186
+ primitiveHandlers = new Map();
187
+ primitiveFallbackHandler = null;
39
188
  /** Contact-graph operations (`agent.contacts.*`). Initialized in the constructor. */
40
189
  contacts;
41
190
  /** Block/unblock operations (`agent.users.*`). Initialized in the constructor. */
@@ -48,6 +197,7 @@ export class CanonAgent {
48
197
  runtimeHeartbeatTimer = null;
49
198
  runtimeControlPollTimer = null;
50
199
  lastSeenSignal = new Map();
200
+ primitiveRequestDedupe = new Map();
51
201
  activeAbortControllers = new Map();
52
202
  conversationMemberIds = new Map();
53
203
  pendingMembershipChanges = new Map();
@@ -84,6 +234,14 @@ export class CanonAgent {
84
234
  this.interruptHandler = options.runtimeControls?.onInterrupt ?? null;
85
235
  this.stopAndDropHandler = options.runtimeControls?.onStopAndDrop ?? null;
86
236
  this.newSessionHandler = options.runtimeControls?.onNewSession ?? null;
237
+ for (const [primitive, handler] of Object.entries(options.runtimePrimitives ?? {})) {
238
+ if (primitive === '*') {
239
+ this.primitiveFallbackHandler = handler;
240
+ }
241
+ else if (isRuntimePrimitiveId(primitive)) {
242
+ this.primitiveHandlers.set(primitive, handler);
243
+ }
244
+ }
87
245
  }
88
246
  on(event, handler) {
89
247
  if (event === 'message') {
@@ -134,6 +292,44 @@ export class CanonAgent {
134
292
  }
135
293
  this.contactRemovedHandler = handler;
136
294
  }
295
+ onPrimitive(primitive, handler) {
296
+ if (primitive === '*') {
297
+ this.primitiveFallbackHandler = handler;
298
+ }
299
+ else {
300
+ this.primitiveHandlers.set(primitive, handler);
301
+ }
302
+ if (this.running) {
303
+ this.startRuntimeControlPolling();
304
+ }
305
+ void this.publishAgentRuntime().catch(() => { });
306
+ }
307
+ describeCommands(_provider) {
308
+ return this.buildRuntimeDescriptor().commands ?? [];
309
+ }
310
+ async publishRuntimeFacts(conversationId, facts) {
311
+ this.rememberConversationId(conversationId);
312
+ const publisher = this.requireRuntimeStatePublisher();
313
+ const normalizedFacts = facts
314
+ .map(normalizeRuntimeFact)
315
+ .filter((fact) => Boolean(fact));
316
+ await publisher.patchRuntimeInfo(conversationId, {
317
+ descriptor: this.buildRuntimeDescriptor(),
318
+ facts: normalizedFacts,
319
+ });
320
+ }
321
+ async publishRuntimeActivity(conversationId, item) {
322
+ this.rememberConversationId(conversationId);
323
+ const normalized = normalizeRuntimeActivityItem(item);
324
+ if (!normalized.id) {
325
+ throw new Error('Runtime activity item id is required.');
326
+ }
327
+ await this.requireRuntimeStatePublisher().writeRuntimeActivity(conversationId, normalized);
328
+ }
329
+ async clearRuntimeActivity(conversationId, options) {
330
+ this.rememberConversationId(conversationId);
331
+ await this.requireRuntimeStatePublisher().clearRuntimeActivity(conversationId, options);
332
+ }
137
333
  /**
138
334
  * Resolve admission live for a target user (typically read off a shared
139
335
  * contact card) and route into either an immediate message or a contact
@@ -371,6 +567,12 @@ export class CanonAgent {
371
567
  hasRuntimeSignalSupport() {
372
568
  return this.hasInterruptSupport() || this.hasStopAndDropSupport() || this.hasNewSessionSupport();
373
569
  }
570
+ hasRuntimePrimitiveSupport() {
571
+ return this.primitiveHandlers.size > 0 || Boolean(this.primitiveFallbackHandler);
572
+ }
573
+ hasRuntimeControlSupport() {
574
+ return this.hasRuntimeSignalSupport() || this.hasRuntimePrimitiveSupport();
575
+ }
374
576
  buildRuntimeDescriptor() {
375
577
  const source = this.options.runtimeDescriptor ?? DEFAULT_SDK_RUNTIME_DESCRIPTOR;
376
578
  const hasInterrupt = this.hasInterruptSupport();
@@ -399,9 +601,23 @@ export class CanonAgent {
399
601
  if (hasNewSession && !hasNewSessionAction) {
400
602
  actions.push(RUNTIME_NEW_SESSION_ACTION);
401
603
  }
604
+ const commands = [...(source.commands ?? [])].filter((command) => {
605
+ if (command.dispatch.kind !== 'primitive')
606
+ return true;
607
+ return this.primitiveHandlers.has(command.dispatch.primitive)
608
+ || Boolean(this.primitiveFallbackHandler);
609
+ });
610
+ const hasCommandForPrimitive = (primitive) => commands.some((command) => (command.primitive === primitive
611
+ || (command.dispatch.kind === 'primitive' && command.dispatch.primitive === primitive)));
612
+ for (const primitive of this.primitiveHandlers.keys()) {
613
+ if (!hasCommandForPrimitive(primitive)) {
614
+ commands.push(STANDARD_PRIMITIVE_COMMANDS[primitive]);
615
+ }
616
+ }
402
617
  return {
403
618
  ...source,
404
619
  supportsInterrupt: hasInterrupt,
620
+ commands,
405
621
  actions,
406
622
  };
407
623
  }
@@ -493,10 +709,10 @@ export class CanonAgent {
493
709
  }));
494
710
  }
495
711
  startRuntimeControlPolling() {
496
- if (!this.agentId || this.runtimeControlPollTimer || !this.hasRuntimeSignalSupport())
712
+ if (!this.agentId || this.runtimeControlPollTimer || !this.hasRuntimeControlSupport())
497
713
  return;
498
714
  this.runtimeControlPollTimer = setInterval(() => {
499
- void this.pollRuntimeControlSignals();
715
+ void this.pollRuntimeControls();
500
716
  }, 2_000);
501
717
  this.runtimeControlPollTimer.unref?.();
502
718
  }
@@ -506,16 +722,97 @@ export class CanonAgent {
506
722
  clearInterval(this.runtimeControlPollTimer);
507
723
  this.runtimeControlPollTimer = null;
508
724
  }
509
- async pollRuntimeControlSignals() {
510
- if (!this.agentId || !this.hasRuntimeSignalSupport())
725
+ async pollRuntimeControls() {
726
+ if (!this.agentId || !this.hasRuntimeControlSupport())
511
727
  return;
512
728
  await Promise.all(this.cachedConversationIds.map(async (conversationId) => {
513
- const raw = await Promise.resolve(rtdbRead(`/control/${conversationId}/${this.agentId}/signal`)).catch(() => null);
514
- if (!raw || typeof raw !== 'object')
515
- return;
516
- await this.handleRuntimeSignal(conversationId, raw);
729
+ if (this.hasRuntimeSignalSupport()) {
730
+ const raw = await Promise.resolve(rtdbRead(`/control/${conversationId}/${this.agentId}/signal`)).catch(() => null);
731
+ if (raw && typeof raw === 'object') {
732
+ await this.handleRuntimeSignal(conversationId, raw);
733
+ }
734
+ }
735
+ if (this.hasRuntimePrimitiveSupport()) {
736
+ const raw = await Promise.resolve(rtdbRead(`/control/${conversationId}/${this.agentId}/primitive`)).catch(() => null);
737
+ if (raw && typeof raw === 'object') {
738
+ await this.handleRuntimePrimitiveRequests(conversationId, raw);
739
+ }
740
+ }
517
741
  }));
518
742
  }
743
+ async handleRuntimePrimitiveRequests(conversationId, raw) {
744
+ if (!this.agentId)
745
+ return;
746
+ this.prunePrimitiveRequestDedupe();
747
+ const requests = Object.entries(raw)
748
+ .map(([requestId, value]) => ({ requestId, value }))
749
+ .filter((entry) => (Boolean(entry.requestId)
750
+ && Boolean(entry.value)
751
+ && typeof entry.value === 'object'
752
+ && !Array.isArray(entry.value)))
753
+ .sort((a, b) => Number(a.value.updatedAt ?? 0) - Number(b.value.updatedAt ?? 0));
754
+ for (const { requestId, value } of requests) {
755
+ const requestKey = `${conversationId}:${requestId}`;
756
+ if (this.primitiveRequestDedupe.has(requestKey))
757
+ continue;
758
+ this.primitiveRequestDedupe.set(requestKey, Date.now());
759
+ let cleared = false;
760
+ try {
761
+ const primitive = value.id;
762
+ if (!isRuntimePrimitiveId(primitive)) {
763
+ cleared = await this.clearRuntimePrimitiveRequest(conversationId, requestId);
764
+ continue;
765
+ }
766
+ const handler = this.primitiveHandlers.get(primitive) ?? this.primitiveFallbackHandler;
767
+ if (!handler) {
768
+ cleared = await this.clearRuntimePrimitiveRequest(conversationId, requestId);
769
+ continue;
770
+ }
771
+ const args = normalizePrimitiveArgs(value.args);
772
+ await Promise.resolve(handler({
773
+ conversationId,
774
+ primitive,
775
+ args,
776
+ requestId,
777
+ updatedAt: typeof value.updatedAt === 'number' ? value.updatedAt : undefined,
778
+ rawText: typeof value.rawText === 'string' ? value.rawText : undefined,
779
+ alias: typeof value.alias === 'string' ? value.alias : undefined,
780
+ })).catch((error) => {
781
+ console.error(`[canon-sdk] Runtime primitive ${primitive} handler failed for ${conversationId}:`, error);
782
+ });
783
+ cleared = await this.clearRuntimePrimitiveRequest(conversationId, requestId);
784
+ }
785
+ finally {
786
+ if (cleared) {
787
+ this.primitiveRequestDedupe.delete(requestKey);
788
+ }
789
+ }
790
+ }
791
+ }
792
+ async clearRuntimePrimitiveRequest(conversationId, requestId) {
793
+ if (!this.agentId)
794
+ return false;
795
+ try {
796
+ await Promise.resolve(rtdbWrite(`/control/${conversationId}/${this.agentId}/primitive/${requestId}`, null));
797
+ return true;
798
+ }
799
+ catch {
800
+ return false;
801
+ }
802
+ }
803
+ prunePrimitiveRequestDedupe(now = Date.now()) {
804
+ for (const [key, timestamp] of this.primitiveRequestDedupe) {
805
+ if (now - timestamp >= RUNTIME_PRIMITIVE_DEDUPE_TTL_MS) {
806
+ this.primitiveRequestDedupe.delete(key);
807
+ }
808
+ }
809
+ while (this.primitiveRequestDedupe.size > RUNTIME_PRIMITIVE_DEDUPE_MAX) {
810
+ const oldestKey = this.primitiveRequestDedupe.keys().next().value;
811
+ if (!oldestKey)
812
+ break;
813
+ this.primitiveRequestDedupe.delete(oldestKey);
814
+ }
815
+ }
519
816
  async handleRuntimeSignal(conversationId, raw) {
520
817
  if (!this.agentId)
521
818
  return;
@@ -594,6 +891,13 @@ export class CanonAgent {
594
891
  hostMode: false,
595
892
  });
596
893
  }
894
+ requireRuntimeStatePublisher() {
895
+ const publisher = this.createRuntimeStatePublisher();
896
+ if (!publisher) {
897
+ throw new Error('Canon agent must be started before publishing runtime operations.');
898
+ }
899
+ return publisher;
900
+ }
597
901
  async handleMessages(conversationId, messages) {
598
902
  if (!this.handler) {
599
903
  console.warn(`[canon-sdk] No message handler registered — messages for ${conversationId} dropped. Call agent.on('message', handler) before starting.`);
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  export { CanonAgent } from './canon-agent.js';
2
2
  export type { AgentContactsAPI, AgentUsersAPI } from './canon-agent.js';
3
3
  export { CanonApiError, HOST_ADMISSION_ACTION_CAPABILITIES, HOST_ADMISSION_ACTIONS_DISABLED, } from '@canonmsg/core';
4
- export type { CanonContact, CanonResolveAdmissionResult, ContactAddedPayload, ContactCardPayload, ContactRemovedPayload, ContactSource, HostAdmissionActionCapabilities, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, } from '@canonmsg/core';
4
+ export type { CanonContact, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeFact, CanonRuntimeFactGroup, CanonResolveAdmissionResult, ContactAddedPayload, ContactCardPayload, ContactRemovedPayload, ContactSource, HostAdmissionActionCapabilities, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, } from '@canonmsg/core';
5
5
  export { SessionManager } from './session-manager.js';
6
6
  export { DEFAULT_MEDIA_CACHE_DIR, getCodexImagePath, getMessageAttachments, inferUploadMimeType, isAnthropicImageAttachment, materializeAttachment, materializeMessageMedia, resolveAttachmentMimeType, sendMediaFileMessage, toAnthropicImageBlock, uploadMediaFile, } from './media.js';
7
7
  export type { AnthropicImageBlock, AnthropicImageMimeType, MaterializeMediaOptions, MaterializedCanonAttachment, ReplyWithFileOptions, UploadMediaFileOptions, } from './media.js';
8
8
  export type { SessionConfig, Session } from './session-manager.js';
9
9
  export type { AgentContext, CanonGroupContext, CanonKnownRecentParticipant, CanonMembershipChange, CanonContactRequest, CanonMessage, CanonConversation, CanonSelfContext, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, SendMessageOptions, CreateConversationOptions, } from '@canonmsg/core';
10
- export type { CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, ContactRequestHandler, MessageHandler, MessageHandlerContext, ProgressMessageOptions, ProgressMessageResult, ReachOutOptions, ReachOutResult, SessionInfo, SessionOptions, DeliveryMode, } from './types.js';
10
+ export type { CanonAgentOptions, ContactAddedHandler, ContactRemovedHandler, ContactRequestHandler, MessageHandler, MessageHandlerContext, ProgressMessageOptions, ProgressMessageResult, ReachOutOptions, ReachOutResult, RuntimePrimitiveContext, RuntimePrimitiveHandler, RuntimePrimitiveHandlers, SessionInfo, SessionOptions, DeliveryMode, } from './types.js';
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export type { AddMemberResult, AgentClientType, CanonGroupContext, CanonRuntimeDescriptor, CanonMessage, CanonConversation, CanonContact, CanonContactRequest, CanonResolveAdmissionResult, ContactAddedPayload, ContactRemovedPayload, ContactSource, AgentContext, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonSelfContext, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, SendMessageOptions, SessionConfig, CreateConversationOptions, TurnLifecycleState, } from '@canonmsg/core';
2
- import type { AddMemberResult, CanonGroupContext, CanonMessage, CanonConversation, ContactCardPayload, CanonRuntimeActionDispatch, SendMessageOptions, SendContextualSelfContextInput, SessionConfig } from '@canonmsg/core';
1
+ export type { AddMemberResult, AgentClientType, CanonGroupContext, CanonRuntimeActivityItem, CanonRuntimeActivityKind, CanonRuntimeActivityStatus, CanonRuntimeDescriptor, CanonRuntimeFact, CanonRuntimeFactGroup, CanonRuntimePrimitiveId, CanonMessage, CanonConversation, CanonContact, CanonContactRequest, CanonResolveAdmissionResult, ContactAddedPayload, ContactRemovedPayload, ContactSource, AgentContext, ResolvedAdmissionState, ResolvedAdmissionTargetSummary, ResolvedTargetAdmissionPayload, CanonSelfContext, SendContextualMessageOptions, SendContextualMessageResult, SendContextualSelfContextInput, SendMessageOptions, SessionConfig, CreateConversationOptions, TurnLifecycleState, } from '@canonmsg/core';
2
+ import type { AddMemberResult, CanonGroupContext, CanonMessage, CanonConversation, ContactCardPayload, CanonRuntimeActionDispatch, CanonRuntimePrimitiveId, SendMessageOptions, SendContextualSelfContextInput, SessionConfig } from '@canonmsg/core';
3
3
  import type { MaterializeMediaOptions, MaterializedCanonAttachment, ReplyWithFileOptions, UploadMediaFileOptions } from './media.js';
4
4
  export interface ProgressMessageOptions extends SendMessageOptions {
5
5
  /**
@@ -123,6 +123,19 @@ export interface RuntimeControlHandlers {
123
123
  onStopAndDrop?: RuntimeSignalHandler;
124
124
  onNewSession?: RuntimeSignalHandler;
125
125
  }
126
+ export interface RuntimePrimitiveContext {
127
+ conversationId: string;
128
+ primitive: CanonRuntimePrimitiveId;
129
+ args: Record<string, string | boolean>;
130
+ rawText?: string;
131
+ alias?: string;
132
+ requestId?: string;
133
+ updatedAt?: number;
134
+ }
135
+ export type RuntimePrimitiveHandler = (context: RuntimePrimitiveContext) => void | Promise<void>;
136
+ export type RuntimePrimitiveHandlers = Partial<Record<CanonRuntimePrimitiveId, RuntimePrimitiveHandler>> & {
137
+ '*'?: RuntimePrimitiveHandler;
138
+ };
126
139
  export interface CanonAgentOptions {
127
140
  apiKey: string;
128
141
  baseUrl?: string;
@@ -141,6 +154,8 @@ export interface CanonAgentOptions {
141
154
  runtimeDescriptor?: import('@canonmsg/core').CanonRuntimeDescriptor;
142
155
  /** Optional Canon runtime signal handlers. Enables interrupt controls when provided. */
143
156
  runtimeControls?: RuntimeControlHandlers;
157
+ /** Optional typed runtime primitive handlers. Enables descriptor-backed command controls when provided. */
158
+ runtimePrimitives?: RuntimePrimitiveHandlers;
144
159
  /**
145
160
  * Enable RTDB session-state reporting. Off by default.
146
161
  * Turn-state reporting is automatic while handlers run.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/agent-sdk",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Canon Agent SDK — build AI agents that participate in Canon conversations",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -28,7 +28,7 @@
28
28
  "node": ">=18.0.0"
29
29
  },
30
30
  "dependencies": {
31
- "@canonmsg/core": "^0.17.0"
31
+ "@canonmsg/core": "^0.18.0"
32
32
  },
33
33
  "publishConfig": {
34
34
  "access": "public"