@canonmsg/core 0.15.3 → 0.15.4

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,4 +1,4 @@
1
- import type { CanonResolveAdmissionResult, CreateContactRequestResult, CreateConversationOptions, SendMessageOptions } from './types.js';
1
+ import type { CanonResolveAdmissionResult, CreateContactRequestResult, CreateConversationOptions, SendMessageOptions, SessionConfig } from './types.js';
2
2
  export interface CanonReachOutClient {
3
3
  resolveAdmission(targetUserId: string): Promise<CanonResolveAdmissionResult>;
4
4
  createConversation(options: CreateConversationOptions): Promise<{
@@ -14,6 +14,7 @@ export interface CanonReachOutOptions {
14
14
  text?: string | null;
15
15
  requestMessage?: string | null;
16
16
  sendMessageOptions?: SendMessageOptions;
17
+ sessionConfig?: SessionConfig | null;
17
18
  }
18
19
  export type CanonReachOutResult = {
19
20
  status: 'messaged';
@@ -25,6 +26,9 @@ export type CanonReachOutResult = {
25
26
  } | {
26
27
  status: 'pending';
27
28
  requestId: string | null;
29
+ } | {
30
+ status: 'setup_required';
31
+ reason: string;
28
32
  } | {
29
33
  status: 'blocked' | 'unavailable';
30
34
  reason: string;
package/dist/reach-out.js CHANGED
@@ -11,6 +11,12 @@ function normalizeContactRequestMessage(requestMessage, fallbackText) {
11
11
  return text;
12
12
  return `${text.slice(0, CONTACT_REQUEST_MESSAGE_LIMIT - 3)}...`;
13
13
  }
14
+ function optionsForResolvedTarget(options, targetUserType) {
15
+ if (options.sessionConfig && targetUserType === 'human') {
16
+ return { ...options, sessionConfig: null };
17
+ }
18
+ return options;
19
+ }
14
20
  function isConnectionRequiredError(error) {
15
21
  const status = error && typeof error === 'object' && 'status' in error
16
22
  ? Number(error.status)
@@ -18,10 +24,34 @@ function isConnectionRequiredError(error) {
18
24
  const message = error instanceof Error ? error.message : String(error ?? '');
19
25
  return status === 403 && /CONNECTION_REQUIRED|connection required/i.test(message);
20
26
  }
27
+ function isSessionSetupRequiredError(error) {
28
+ const status = error && typeof error === 'object' && 'status' in error
29
+ ? Number(error.status)
30
+ : null;
31
+ if (status !== 400 && status !== 403)
32
+ return null;
33
+ const rawMessage = error instanceof Error ? error.message : String(error ?? '');
34
+ const message = rawMessage.replace(/^Canon API error \d+:\s*/i, '').trim();
35
+ const parsed = (() => {
36
+ try {
37
+ const value = JSON.parse(message);
38
+ return typeof value.error === 'string' ? value.error : message;
39
+ }
40
+ catch {
41
+ return message;
42
+ }
43
+ })();
44
+ if (/session config|execution mode|coding session|worktree|workspace|permission mode|agent owner|runtime unavailable|unsupported control|invalid .*mode|invalid .*model/i
45
+ .test(parsed)) {
46
+ return parsed || 'Agent session setup is required.';
47
+ }
48
+ return null;
49
+ }
21
50
  async function openConversationAndMaybeMessage(client, options) {
22
51
  const { conversationId } = await client.createConversation({
23
52
  type: 'direct',
24
53
  targetUserId: options.targetUserId,
54
+ ...(options.sessionConfig ? { sessionConfig: options.sessionConfig } : {}),
25
55
  });
26
56
  const text = normalizeOptionalText(options.text);
27
57
  if (text) {
@@ -35,7 +65,16 @@ async function openConversationAndMaybeMessage(client, options) {
35
65
  async function requestContact(client, options) {
36
66
  const result = await client.createContactRequest(options.targetUserId, normalizeContactRequestMessage(options.requestMessage, options.text));
37
67
  if (result.status === 'open') {
38
- return openConversationAndMaybeMessage(client, { ...options, requestMessage: null });
68
+ try {
69
+ return await openConversationAndMaybeMessage(client, { ...options, requestMessage: null });
70
+ }
71
+ catch (error) {
72
+ const setupReason = isSessionSetupRequiredError(error);
73
+ if (setupReason) {
74
+ return { status: 'setup_required', reason: setupReason };
75
+ }
76
+ throw error;
77
+ }
39
78
  }
40
79
  if (result.status === 'duplicate') {
41
80
  return { status: 'pending', requestId: result.requestId };
@@ -48,14 +87,19 @@ async function requestContact(client, options) {
48
87
  * their product behavior is "message if reachable, otherwise request access."
49
88
  */
50
89
  export async function reachOutToCanonContact(client, options) {
51
- const { admission } = await client.resolveAdmission(options.targetUserId);
90
+ const { target, admission } = await client.resolveAdmission(options.targetUserId);
91
+ const resolvedOptions = optionsForResolvedTarget(options, target?.userType);
52
92
  if (admission.state === 'allowed' && admission.canMessage) {
53
93
  try {
54
- return await openConversationAndMaybeMessage(client, options);
94
+ return await openConversationAndMaybeMessage(client, resolvedOptions);
55
95
  }
56
96
  catch (error) {
97
+ const setupReason = isSessionSetupRequiredError(error);
98
+ if (setupReason) {
99
+ return { status: 'setup_required', reason: setupReason };
100
+ }
57
101
  if (isConnectionRequiredError(error)) {
58
- return requestContact(client, options);
102
+ return requestContact(client, resolvedOptions);
59
103
  }
60
104
  throw error;
61
105
  }
@@ -64,7 +108,7 @@ export async function reachOutToCanonContact(client, options) {
64
108
  return { status: 'pending', requestId: admission.pendingRequestId ?? null };
65
109
  }
66
110
  if (admission.state === 'request-required' && admission.canRequestContact) {
67
- return requestContact(client, options);
111
+ return requestContact(client, resolvedOptions);
68
112
  }
69
113
  if (admission.state === 'blocked') {
70
114
  return { status: 'blocked', reason: 'blocked' };
package/dist/types.d.ts CHANGED
@@ -30,6 +30,7 @@ export interface ContactCardPayload {
30
30
  displayName: string;
31
31
  avatarUrl: string | null;
32
32
  userType: 'human' | 'ai_agent';
33
+ clientType?: AgentClientType;
33
34
  about?: string;
34
35
  isActive?: boolean;
35
36
  ownerId?: string;
@@ -461,6 +462,8 @@ export interface CreateConversationOptions {
461
462
  targetUserId?: string;
462
463
  memberIds?: string[];
463
464
  name?: string;
465
+ /** Required when creating a direct conversation with a first-party coding agent. */
466
+ sessionConfig?: SessionConfig | null;
464
467
  }
465
468
  export type StreamingStatus = 'thinking' | 'streaming' | 'tool';
466
469
  export interface SetStreamingOptions {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canonmsg/core",
3
- "version": "0.15.3",
3
+ "version": "0.15.4",
4
4
  "description": "Canon core — shared types, REST client, SSE stream, and registration for Canon messaging",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",