@canonmsg/core 0.6.0 → 0.7.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/dist/base-url.d.ts +1 -0
- package/dist/base-url.js +9 -0
- package/dist/execution-environment.d.ts +2 -2
- package/dist/execution-environment.js +18 -23
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/rtdb-rest.d.ts +20 -2
- package/dist/rtdb-rest.js +175 -130
- package/package.json +3 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveCanonBaseUrl(input?: string | null): string;
|
package/dist/base-url.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DEFAULT_BASE_URL } from './constants.js';
|
|
2
|
+
export function resolveCanonBaseUrl(input) {
|
|
3
|
+
if (typeof input !== 'string')
|
|
4
|
+
return DEFAULT_BASE_URL;
|
|
5
|
+
const trimmed = input.trim();
|
|
6
|
+
if (!trimmed)
|
|
7
|
+
return DEFAULT_BASE_URL;
|
|
8
|
+
return trimmed.replace(/\/+$/, '');
|
|
9
|
+
}
|
|
@@ -23,8 +23,8 @@ export interface ConfiguredWorkspaceOption extends WorkspaceResolverOption {
|
|
|
23
23
|
}
|
|
24
24
|
export interface SessionWorkspaceConfig {
|
|
25
25
|
workspaceId?: string;
|
|
26
|
-
legacyCwd?: string;
|
|
27
26
|
model?: string;
|
|
27
|
+
retiredWorkspaceConfig?: boolean;
|
|
28
28
|
}
|
|
29
29
|
export declare function normalizeOptionalString(value: unknown): string | undefined;
|
|
30
30
|
export declare function isEnabledFlag(value: unknown): boolean;
|
|
@@ -37,7 +37,7 @@ export declare function resolveConfiguredWorkspaceCwd(input: {
|
|
|
37
37
|
workspaceOptions: WorkspaceResolverOption[];
|
|
38
38
|
config: {
|
|
39
39
|
workspaceId?: string;
|
|
40
|
-
|
|
40
|
+
retiredWorkspaceConfig?: boolean;
|
|
41
41
|
} | null;
|
|
42
42
|
defaultCwd: string;
|
|
43
43
|
}): string;
|
|
@@ -153,48 +153,43 @@ export function buildConfiguredWorkspaceOptions(primaryCwd, configured) {
|
|
|
153
153
|
export function buildPublicWorkspaceOptions(workspaceOptions) {
|
|
154
154
|
return workspaceOptions.map(({ id, label }) => ({ id, label }));
|
|
155
155
|
}
|
|
156
|
+
function isRetiredWorkspaceId(value) {
|
|
157
|
+
if (!value)
|
|
158
|
+
return false;
|
|
159
|
+
return value === 'default' || /^workspace-\d+$/.test(value);
|
|
160
|
+
}
|
|
156
161
|
export function readSessionWorkspaceConfig(raw) {
|
|
157
162
|
if (!raw || typeof raw !== 'object')
|
|
158
163
|
return null;
|
|
159
164
|
const data = raw;
|
|
165
|
+
const rawWorkspaceId = normalizeOptionalString(data.workspaceId);
|
|
166
|
+
const stableWorkspaceId = rawWorkspaceId && !isRetiredWorkspaceId(rawWorkspaceId)
|
|
167
|
+
? rawWorkspaceId
|
|
168
|
+
: undefined;
|
|
169
|
+
const hasRetiredWorkspaceReference = (normalizeOptionalString(data.cwd) !== undefined
|
|
170
|
+
|| isRetiredWorkspaceId(rawWorkspaceId));
|
|
160
171
|
return {
|
|
161
|
-
workspaceId:
|
|
162
|
-
legacyCwd: normalizeOptionalString(data.cwd),
|
|
172
|
+
workspaceId: stableWorkspaceId,
|
|
163
173
|
model: normalizeOptionalString(data.model),
|
|
174
|
+
...(!stableWorkspaceId && hasRetiredWorkspaceReference
|
|
175
|
+
? { retiredWorkspaceConfig: true }
|
|
176
|
+
: {}),
|
|
164
177
|
};
|
|
165
178
|
}
|
|
166
|
-
function findWorkspaceByLegacyCwd(workspaceOptions, legacyCwd) {
|
|
167
|
-
if (!legacyCwd)
|
|
168
|
-
return undefined;
|
|
169
|
-
const resolvedLegacyCwd = resolve(legacyCwd);
|
|
170
|
-
return workspaceOptions.find((workspace) => resolve(workspace.cwd) === resolvedLegacyCwd);
|
|
171
|
-
}
|
|
172
179
|
export function resolveConfiguredWorkspaceCwd(input) {
|
|
173
180
|
const fallbackCwd = input.workspaceOptions[0]?.cwd ?? resolve(input.defaultCwd);
|
|
174
181
|
if (!input.config)
|
|
175
182
|
return fallbackCwd;
|
|
176
|
-
|
|
183
|
+
if (input.config.retiredWorkspaceConfig) {
|
|
184
|
+
throw new ExecutionEnvironmentError('Session config still references a retired workspace format.', 'This Canon coding session was saved with a retired workspace format. Recreate the session or select a current workspace.');
|
|
185
|
+
}
|
|
177
186
|
const workspaceId = input.config.workspaceId;
|
|
178
187
|
if (workspaceId) {
|
|
179
188
|
const workspace = input.workspaceOptions.find((option) => option.id === workspaceId);
|
|
180
189
|
if (workspace)
|
|
181
190
|
return workspace.cwd;
|
|
182
|
-
if (workspaceId === 'default') {
|
|
183
|
-
return fallbackCwd;
|
|
184
|
-
}
|
|
185
|
-
const legacyWorkspaceMatch = /^workspace-(\d+)$/.exec(workspaceId);
|
|
186
|
-
if (legacyWorkspaceMatch) {
|
|
187
|
-
const legacyIndex = Number.parseInt(legacyWorkspaceMatch[1] ?? '', 10) - 1;
|
|
188
|
-
if (legacyIndex >= 0 && input.workspaceOptions[legacyIndex]) {
|
|
189
|
-
return input.workspaceOptions[legacyIndex].cwd;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
if (legacyWorkspace)
|
|
193
|
-
return legacyWorkspace.cwd;
|
|
194
191
|
throw new ExecutionEnvironmentError(`Workspace ${workspaceId} is not configured on this machine.`, 'The workspace saved for this Canon coding session is no longer configured on this machine.');
|
|
195
192
|
}
|
|
196
|
-
if (legacyWorkspace)
|
|
197
|
-
return legacyWorkspace.cwd;
|
|
198
193
|
return fallbackCwd;
|
|
199
194
|
}
|
|
200
195
|
export function buildConversationWorktreeSpec(input) {
|
package/dist/index.d.ts
CHANGED
|
@@ -25,3 +25,4 @@ export type { ConfiguredWorkspaceOption, ExecutionEnvironmentMode, PreparedExecu
|
|
|
25
25
|
export { initRTDBAuth, rtdbWrite, rtdbRead, writeSessionState, clearSessionState, writeTurnState, clearTurnState, } from './rtdb-rest.js';
|
|
26
26
|
export type { SessionStatePayload, TurnStatePayload } from './rtdb-rest.js';
|
|
27
27
|
export { DEFAULT_BASE_URL, DEFAULT_STREAM_URL, DEFAULT_RTDB_URL, FIREBASE_WEB_API_KEY } from './constants.js';
|
|
28
|
+
export { resolveCanonBaseUrl } from './base-url.js';
|
package/dist/index.js
CHANGED
|
@@ -26,3 +26,5 @@ export { buildConfiguredWorkspaceOptions, buildConversationEnvironmentKey, build
|
|
|
26
26
|
export { initRTDBAuth, rtdbWrite, rtdbRead, writeSessionState, clearSessionState, writeTurnState, clearTurnState, } from './rtdb-rest.js';
|
|
27
27
|
// Constants
|
|
28
28
|
export { DEFAULT_BASE_URL, DEFAULT_STREAM_URL, DEFAULT_RTDB_URL, FIREBASE_WEB_API_KEY } from './constants.js';
|
|
29
|
+
// Base URL resolver
|
|
30
|
+
export { resolveCanonBaseUrl } from './base-url.js';
|
package/dist/rtdb-rest.d.ts
CHANGED
|
@@ -50,8 +50,25 @@ export interface TurnStatePayload {
|
|
|
50
50
|
'.sv': 'timestamp';
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
interface RTDBAuthOptions {
|
|
54
|
+
rtdbUrl?: string;
|
|
55
|
+
firebaseApiKey?: string;
|
|
56
|
+
}
|
|
57
|
+
interface RTDBClientHandle {
|
|
58
|
+
read(path: string): Promise<unknown>;
|
|
59
|
+
write(path: string, data: unknown): Promise<void>;
|
|
60
|
+
patch(path: string, data: unknown): Promise<void>;
|
|
61
|
+
remove(path: string): Promise<void>;
|
|
62
|
+
writeSessionState(conversationId: string, agentId: string, state: Omit<SessionStatePayload, 'updatedAt'>): Promise<void>;
|
|
63
|
+
clearSessionState(conversationId: string, agentId: string): Promise<void>;
|
|
64
|
+
writeTurnState(conversationId: string, agentId: string, state: Omit<TurnStatePayload, 'updatedAt'>): Promise<void>;
|
|
65
|
+
clearTurnState(conversationId: string, agentId: string): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Initializes the default RTDB helper and returns a scoped client for callers
|
|
69
|
+
* that need per-runtime auth and base-url isolation.
|
|
70
|
+
*/
|
|
71
|
+
export declare function initRTDBAuth(client: CanonClient, options?: RTDBAuthOptions): RTDBClientHandle;
|
|
55
72
|
/** Generic RTDB REST write (PUT). */
|
|
56
73
|
export declare function rtdbWrite(path: string, data: unknown): Promise<void>;
|
|
57
74
|
/** Generic RTDB REST read (GET). */
|
|
@@ -67,3 +84,4 @@ export declare function writeSessionState(conversationId: string, agentId: strin
|
|
|
67
84
|
export declare function clearSessionState(conversationId: string, agentId: string): Promise<void>;
|
|
68
85
|
export declare function writeTurnState(conversationId: string, agentId: string, state: Omit<TurnStatePayload, 'updatedAt'>): Promise<void>;
|
|
69
86
|
export declare function clearTurnState(conversationId: string, agentId: string): Promise<void>;
|
|
87
|
+
export {};
|
package/dist/rtdb-rest.js
CHANGED
|
@@ -6,163 +6,208 @@
|
|
|
6
6
|
* is exchanged for a Firebase ID token before use with RTDB REST.
|
|
7
7
|
*/
|
|
8
8
|
import { DEFAULT_RTDB_URL, FIREBASE_WEB_API_KEY } from './constants.js';
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
let tokenClient = null;
|
|
15
|
-
/** Must be called once before any RTDB operations. */
|
|
16
|
-
export function initRTDBAuth(client) {
|
|
17
|
-
tokenClient = client;
|
|
9
|
+
const DEFAULT_RTDB_BASE = normalizeRTDBBase(process.env.CANON_RTDB_URL || DEFAULT_RTDB_URL);
|
|
10
|
+
const DEFAULT_FIREBASE_API_KEY = process.env.CANON_FIREBASE_API_KEY || FIREBASE_WEB_API_KEY;
|
|
11
|
+
let defaultRTDBClient = null;
|
|
12
|
+
function normalizeRTDBBase(url) {
|
|
13
|
+
return url.replace(/\/+$/, '');
|
|
18
14
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
*/
|
|
22
|
-
async function exchangeCustomTokenForIdToken(customToken) {
|
|
23
|
-
const url = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${FIREBASE_API_KEY}`;
|
|
24
|
-
const res = await fetch(url, {
|
|
25
|
-
method: 'POST',
|
|
26
|
-
headers: { 'Content-Type': 'application/json' },
|
|
27
|
-
body: JSON.stringify({ token: customToken, returnSecureToken: true }),
|
|
28
|
-
});
|
|
29
|
-
if (!res.ok) {
|
|
30
|
-
const text = await res.text();
|
|
31
|
-
throw new Error(`Token exchange failed (${res.status}): ${text}`);
|
|
32
|
-
}
|
|
33
|
-
const data = await res.json();
|
|
34
|
-
return { idToken: data.idToken, expiresIn: parseInt(data.expiresIn, 10) };
|
|
15
|
+
function normalizeRTDBPath(path) {
|
|
16
|
+
return path.startsWith('/') ? path : `/${path}`;
|
|
35
17
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
18
|
+
function createRTDBClientHandle(client, options) {
|
|
19
|
+
const rtdbBase = normalizeRTDBBase(options?.rtdbUrl || DEFAULT_RTDB_BASE);
|
|
20
|
+
const firebaseApiKey = options?.firebaseApiKey || DEFAULT_FIREBASE_API_KEY;
|
|
21
|
+
let cachedIdToken = null;
|
|
22
|
+
let idTokenExpiresAt = 0;
|
|
23
|
+
let refreshPromise = null;
|
|
24
|
+
async function exchangeCustomTokenForIdToken(customToken) {
|
|
25
|
+
const url = `https://identitytoolkit.googleapis.com/v1/accounts:signInWithCustomToken?key=${firebaseApiKey}`;
|
|
26
|
+
const res = await fetch(url, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: { 'Content-Type': 'application/json' },
|
|
29
|
+
body: JSON.stringify({ token: customToken, returnSecureToken: true }),
|
|
30
|
+
});
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
const text = await res.text();
|
|
33
|
+
throw new Error(`Token exchange failed (${res.status}): ${text}`);
|
|
34
|
+
}
|
|
35
|
+
const data = await res.json();
|
|
36
|
+
return {
|
|
37
|
+
idToken: data.idToken,
|
|
38
|
+
expiresIn: Number.parseInt(data.expiresIn, 10),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
async function getToken(forceRefresh = false) {
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
if (!forceRefresh && cachedIdToken && now <= idTokenExpiresAt - 5 * 60 * 1000) {
|
|
44
|
+
return cachedIdToken;
|
|
45
|
+
}
|
|
46
|
+
if (refreshPromise) {
|
|
47
|
+
return refreshPromise;
|
|
48
|
+
}
|
|
49
|
+
const staleToken = forceRefresh ? null : cachedIdToken;
|
|
50
|
+
refreshPromise = (async () => {
|
|
51
|
+
try {
|
|
52
|
+
const auth = await client.getAuthToken();
|
|
53
|
+
const { idToken, expiresIn } = await exchangeCustomTokenForIdToken(auth.token);
|
|
54
|
+
cachedIdToken = idToken;
|
|
55
|
+
idTokenExpiresAt = Date.now() + (expiresIn * 1000);
|
|
56
|
+
return cachedIdToken;
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
console.error('[canon] RTDB token refresh failed:', err);
|
|
60
|
+
return staleToken;
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
refreshPromise = null;
|
|
64
|
+
}
|
|
65
|
+
})();
|
|
66
|
+
return refreshPromise;
|
|
67
|
+
}
|
|
68
|
+
async function request(method, path, data, options = {}) {
|
|
69
|
+
const token = await getToken(false);
|
|
70
|
+
if (!token)
|
|
41
71
|
return null;
|
|
72
|
+
const url = `${rtdbBase}${normalizeRTDBPath(path)}.json?auth=${encodeURIComponent(token)}`;
|
|
73
|
+
const res = await fetch(url, {
|
|
74
|
+
method,
|
|
75
|
+
...(method === 'GET'
|
|
76
|
+
? {}
|
|
77
|
+
: {
|
|
78
|
+
headers: { 'Content-Type': 'application/json' },
|
|
79
|
+
...(data === undefined ? {} : { body: JSON.stringify(data) }),
|
|
80
|
+
}),
|
|
81
|
+
});
|
|
82
|
+
if (options.retryUnauthorized !== false
|
|
83
|
+
&& (res.status === 401 || res.status === 403)) {
|
|
84
|
+
cachedIdToken = null;
|
|
85
|
+
idTokenExpiresAt = 0;
|
|
86
|
+
const refreshedToken = await getToken(true);
|
|
87
|
+
if (!refreshedToken) {
|
|
88
|
+
return res;
|
|
89
|
+
}
|
|
90
|
+
const retryUrl = `${rtdbBase}${normalizeRTDBPath(path)}.json?auth=${encodeURIComponent(refreshedToken)}`;
|
|
91
|
+
return fetch(retryUrl, {
|
|
92
|
+
method,
|
|
93
|
+
...(method === 'GET'
|
|
94
|
+
? {}
|
|
95
|
+
: {
|
|
96
|
+
headers: { 'Content-Type': 'application/json' },
|
|
97
|
+
...(data === undefined ? {} : { body: JSON.stringify(data) }),
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return res;
|
|
102
|
+
}
|
|
103
|
+
async function requireSuccess(method, path, data, errorPrefix) {
|
|
104
|
+
const res = await request(method, path, data);
|
|
105
|
+
if (!res)
|
|
106
|
+
return;
|
|
107
|
+
if (!res.ok) {
|
|
108
|
+
const text = await res.text();
|
|
109
|
+
throw new Error(`${errorPrefix} (${res.status}): ${text}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function read(path) {
|
|
113
|
+
const res = await request('GET', path);
|
|
114
|
+
if (!res?.ok)
|
|
115
|
+
return null;
|
|
116
|
+
return res.json();
|
|
117
|
+
}
|
|
118
|
+
async function write(path, data) {
|
|
119
|
+
await requireSuccess('PUT', path, data, 'RTDB write failed');
|
|
120
|
+
}
|
|
121
|
+
async function patch(path, data) {
|
|
122
|
+
await requireSuccess('PATCH', path, data, 'RTDB patch failed');
|
|
123
|
+
}
|
|
124
|
+
async function remove(path) {
|
|
125
|
+
await requireSuccess('DELETE', path, undefined, 'RTDB delete failed');
|
|
126
|
+
}
|
|
127
|
+
async function writeSessionStateImpl(conversationId, agentId, state) {
|
|
128
|
+
await write(`/session-state/${conversationId}/${agentId}`, {
|
|
129
|
+
...state,
|
|
130
|
+
updatedAt: { '.sv': 'timestamp' },
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
async function clearSessionStateImpl(conversationId, agentId) {
|
|
42
134
|
try {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
135
|
+
await write(`/session-state/${conversationId}/${agentId}`, {
|
|
136
|
+
isActive: false,
|
|
137
|
+
updatedAt: { '.sv': 'timestamp' },
|
|
138
|
+
});
|
|
47
139
|
}
|
|
48
|
-
catch (
|
|
49
|
-
console.error('[canon] RTDB
|
|
50
|
-
return cachedIdToken; // Return stale token as fallback
|
|
140
|
+
catch (error) {
|
|
141
|
+
console.error('[canon] RTDB clear failed:', error);
|
|
51
142
|
}
|
|
52
143
|
}
|
|
53
|
-
|
|
144
|
+
async function writeTurnStateImpl(conversationId, agentId, state) {
|
|
145
|
+
await write(`/turn-state/${conversationId}/${agentId}`, {
|
|
146
|
+
...state,
|
|
147
|
+
updatedAt: { '.sv': 'timestamp' },
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
async function clearTurnStateImpl(conversationId, agentId) {
|
|
151
|
+
try {
|
|
152
|
+
await write(`/turn-state/${conversationId}/${agentId}`, {
|
|
153
|
+
state: 'idle',
|
|
154
|
+
queueDepth: 0,
|
|
155
|
+
updatedAt: { '.sv': 'timestamp' },
|
|
156
|
+
completedAt: { '.sv': 'timestamp' },
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
console.error('[canon] RTDB turn clear failed:', error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
read,
|
|
165
|
+
write,
|
|
166
|
+
patch,
|
|
167
|
+
remove,
|
|
168
|
+
writeSessionState: writeSessionStateImpl,
|
|
169
|
+
clearSessionState: clearSessionStateImpl,
|
|
170
|
+
writeTurnState: writeTurnStateImpl,
|
|
171
|
+
clearTurnState: clearTurnStateImpl,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Initializes the default RTDB helper and returns a scoped client for callers
|
|
176
|
+
* that need per-runtime auth and base-url isolation.
|
|
177
|
+
*/
|
|
178
|
+
export function initRTDBAuth(client, options) {
|
|
179
|
+
const scopedClient = createRTDBClientHandle(client, options);
|
|
180
|
+
defaultRTDBClient = scopedClient;
|
|
181
|
+
return scopedClient;
|
|
182
|
+
}
|
|
183
|
+
function getDefaultRTDBClient() {
|
|
184
|
+
return defaultRTDBClient;
|
|
54
185
|
}
|
|
55
186
|
// ── RTDB operations ───────────────────────────────────────────────────
|
|
56
187
|
/** Generic RTDB REST write (PUT). */
|
|
57
188
|
export async function rtdbWrite(path, data) {
|
|
58
|
-
|
|
59
|
-
if (!token)
|
|
60
|
-
return;
|
|
61
|
-
const url = `${RTDB_BASE}${path}.json?auth=${token}`;
|
|
62
|
-
const res = await fetch(url, {
|
|
63
|
-
method: 'PUT',
|
|
64
|
-
headers: { 'Content-Type': 'application/json' },
|
|
65
|
-
body: JSON.stringify(data),
|
|
66
|
-
});
|
|
67
|
-
if (!res.ok) {
|
|
68
|
-
const text = await res.text();
|
|
69
|
-
throw new Error(`RTDB write failed (${res.status}): ${text}`);
|
|
70
|
-
}
|
|
189
|
+
await getDefaultRTDBClient()?.write(path, data);
|
|
71
190
|
}
|
|
72
191
|
/** Generic RTDB REST read (GET). */
|
|
73
192
|
export async function rtdbRead(path) {
|
|
74
|
-
|
|
75
|
-
if (!token)
|
|
76
|
-
return null;
|
|
77
|
-
const url = `${RTDB_BASE}${path}.json?auth=${token}`;
|
|
78
|
-
const res = await fetch(url);
|
|
79
|
-
if (!res.ok)
|
|
80
|
-
return null;
|
|
81
|
-
return res.json();
|
|
193
|
+
return getDefaultRTDBClient()?.read(path) ?? null;
|
|
82
194
|
}
|
|
83
195
|
/**
|
|
84
196
|
* Write session state to RTDB via REST API.
|
|
85
197
|
* Path: /session-state/{conversationId}/{agentId}
|
|
86
198
|
*/
|
|
87
199
|
export async function writeSessionState(conversationId, agentId, state) {
|
|
88
|
-
|
|
89
|
-
if (!token)
|
|
90
|
-
return;
|
|
91
|
-
const url = `${RTDB_BASE}/session-state/${conversationId}/${agentId}.json?auth=${token}`;
|
|
92
|
-
const body = {
|
|
93
|
-
...state,
|
|
94
|
-
updatedAt: { '.sv': 'timestamp' },
|
|
95
|
-
};
|
|
96
|
-
const res = await fetch(url, {
|
|
97
|
-
method: 'PUT',
|
|
98
|
-
headers: { 'Content-Type': 'application/json' },
|
|
99
|
-
body: JSON.stringify(body),
|
|
100
|
-
});
|
|
101
|
-
if (!res.ok) {
|
|
102
|
-
const text = await res.text();
|
|
103
|
-
throw new Error(`RTDB write failed (${res.status}): ${text}`);
|
|
104
|
-
}
|
|
200
|
+
await getDefaultRTDBClient()?.writeSessionState(conversationId, agentId, state);
|
|
105
201
|
}
|
|
106
202
|
/**
|
|
107
203
|
* Clear session state from RTDB (full overwrite with isActive: false).
|
|
108
204
|
*/
|
|
109
205
|
export async function clearSessionState(conversationId, agentId) {
|
|
110
|
-
|
|
111
|
-
if (!token)
|
|
112
|
-
return;
|
|
113
|
-
const url = `${RTDB_BASE}/session-state/${conversationId}/${agentId}.json?auth=${token}`;
|
|
114
|
-
const body = {
|
|
115
|
-
isActive: false,
|
|
116
|
-
updatedAt: { '.sv': 'timestamp' },
|
|
117
|
-
};
|
|
118
|
-
// Use PUT (not PATCH) to clear stale fields like model/cwd
|
|
119
|
-
const res = await fetch(url, {
|
|
120
|
-
method: 'PUT',
|
|
121
|
-
headers: { 'Content-Type': 'application/json' },
|
|
122
|
-
body: JSON.stringify(body),
|
|
123
|
-
});
|
|
124
|
-
if (!res.ok) {
|
|
125
|
-
const text = await res.text();
|
|
126
|
-
console.error(`[canon] RTDB clear failed (${res.status}): ${text}`);
|
|
127
|
-
}
|
|
206
|
+
await getDefaultRTDBClient()?.clearSessionState(conversationId, agentId);
|
|
128
207
|
}
|
|
129
208
|
export async function writeTurnState(conversationId, agentId, state) {
|
|
130
|
-
|
|
131
|
-
if (!token)
|
|
132
|
-
return;
|
|
133
|
-
const url = `${RTDB_BASE}/turn-state/${conversationId}/${agentId}.json?auth=${token}`;
|
|
134
|
-
const body = {
|
|
135
|
-
...state,
|
|
136
|
-
updatedAt: { '.sv': 'timestamp' },
|
|
137
|
-
};
|
|
138
|
-
const res = await fetch(url, {
|
|
139
|
-
method: 'PUT',
|
|
140
|
-
headers: { 'Content-Type': 'application/json' },
|
|
141
|
-
body: JSON.stringify(body),
|
|
142
|
-
});
|
|
143
|
-
if (!res.ok) {
|
|
144
|
-
const text = await res.text();
|
|
145
|
-
throw new Error(`RTDB write failed (${res.status}): ${text}`);
|
|
146
|
-
}
|
|
209
|
+
await getDefaultRTDBClient()?.writeTurnState(conversationId, agentId, state);
|
|
147
210
|
}
|
|
148
211
|
export async function clearTurnState(conversationId, agentId) {
|
|
149
|
-
|
|
150
|
-
if (!token)
|
|
151
|
-
return;
|
|
152
|
-
const url = `${RTDB_BASE}/turn-state/${conversationId}/${agentId}.json?auth=${token}`;
|
|
153
|
-
const body = {
|
|
154
|
-
state: 'idle',
|
|
155
|
-
queueDepth: 0,
|
|
156
|
-
updatedAt: { '.sv': 'timestamp' },
|
|
157
|
-
completedAt: { '.sv': 'timestamp' },
|
|
158
|
-
};
|
|
159
|
-
const res = await fetch(url, {
|
|
160
|
-
method: 'PUT',
|
|
161
|
-
headers: { 'Content-Type': 'application/json' },
|
|
162
|
-
body: JSON.stringify(body),
|
|
163
|
-
});
|
|
164
|
-
if (!res.ok) {
|
|
165
|
-
const text = await res.text();
|
|
166
|
-
console.error(`[canon] RTDB turn clear failed (${res.status}): ${text}`);
|
|
167
|
-
}
|
|
212
|
+
await getDefaultRTDBClient()?.clearTurnState(conversationId, agentId);
|
|
168
213
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
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",
|
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
10
|
"import": "./dist/index.js",
|
|
11
|
+
"default": "./dist/index.js",
|
|
11
12
|
"types": "./dist/index.d.ts"
|
|
12
13
|
},
|
|
13
14
|
"./browser": {
|
|
14
15
|
"import": "./dist/browser.js",
|
|
16
|
+
"default": "./dist/browser.js",
|
|
15
17
|
"types": "./dist/browser.d.ts"
|
|
16
18
|
}
|
|
17
19
|
},
|