cozy-client 60.30.1 → 60.30.6

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.
@@ -2025,7 +2025,7 @@ var CozyClient = /*#__PURE__*/function () {
2025
2025
  * Resolves relationships on a document.
2026
2026
  *
2027
2027
  * The original document is kept in the target attribute of
2028
- * the relationship
2028
+ * the relationship.
2029
2029
  *
2030
2030
  * @param {import("./types").CozyClientDocument} document - for which relationships must be resolved
2031
2031
  * @param {Schema} [schemaArg] - The schema describing the relationships
@@ -3126,7 +3126,7 @@ var CozyClient = /*#__PURE__*/function () {
3126
3126
  CozyClient.hooks = CozyClient.hooks || {};
3127
3127
  CozyClient.fetchPolicies = _policies.default; //COZY_CLIENT_VERSION_PACKAGE in replaced by babel. See babel config
3128
3128
 
3129
- CozyClient.version = "60.30.1";
3129
+ CozyClient.version = "60.30.6";
3130
3130
 
3131
3131
  _microee.default.mixin(CozyClient);
3132
3132
 
@@ -7,12 +7,14 @@ Object.defineProperty(exports, "__esModule", {
7
7
  });
8
8
  exports.editAssistant = exports.deleteAssistant = exports.createAssistant = void 0;
9
9
 
10
- var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
11
-
12
10
  var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
13
11
 
14
12
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
15
13
 
14
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
15
+
16
+ var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
17
+
16
18
  var _CozyClient = _interopRequireDefault(require("../CozyClient"));
17
19
 
18
20
  var _dsl = require("../queries/dsl");
@@ -34,8 +36,53 @@ var ACCOUNT_DOCTYPE = 'io.cozy.accounts';
34
36
  * @property {string} baseUrl - Provider's base URL
35
37
  * @property {string} [apiKey] - API key for authentication
36
38
  * @property {string} providerId - ID of the provider
39
+ * @property {string} [providerName] - Display name of the provider, used to
40
+ * name the account. Defaults to the provider id.
41
+ */
42
+
43
+ /**
44
+ * Builds the io.cozy.accounts document backing an assistant.
45
+ *
46
+ * `auth` is the only part the stack encrypts, so it holds the API key alone.
47
+ * The model and base URL are configuration and live in `data`, where they map
48
+ * onto the stack's `llm_override`. `identifier` points at `accountName` so the
49
+ * account is labelled after its provider rather than after `auth.login`.
50
+ *
51
+ * @param {Assistant} assistantData - Data for the assistant
52
+ * @param {object} [provider] - Existing provider account to update
53
+ * @returns {object} The io.cozy.accounts document to save
37
54
  */
38
55
 
56
+ var buildProviderAccount = function buildProviderAccount(assistantData, provider) {
57
+ // `login` used to hold the model: dropped so it stops being encrypted with
58
+ // the API key, and stops outranking `accountName` as the label.
59
+ var _ref = (provider === null || provider === void 0 ? void 0 : provider.auth) || {},
60
+ login = _ref.login,
61
+ password = _ref.password,
62
+ auth = (0, _objectWithoutProperties2.default)(_ref, ["login", "password"]);
63
+
64
+ var _ref2 = (provider === null || provider === void 0 ? void 0 : provider.data) || {},
65
+ baseUrl = _ref2.baseUrl,
66
+ data = (0, _objectWithoutProperties2.default)(_ref2, ["baseUrl"]); // No new key means keeping the one the stack holds in `credentials_encrypted`.
67
+
68
+
69
+ var apiKey = assistantData.apiKey || (assistantData.baseUrl ? password : undefined);
70
+ return _objectSpread(_objectSpread({}, provider), {}, {
71
+ _type: ACCOUNT_DOCTYPE,
72
+ account_type: assistantData.providerId,
73
+ identifier: 'accountName',
74
+ auth: _objectSpread(_objectSpread({}, auth), {}, {
75
+ accountName: assistantData.providerName || assistantData.providerId
76
+ }, apiKey ? {
77
+ password: apiKey
78
+ } : {}),
79
+ data: _objectSpread(_objectSpread({}, data), {}, {
80
+ model: assistantData.model
81
+ }, assistantData.baseUrl ? {
82
+ baseUrl: assistantData.baseUrl
83
+ } : {})
84
+ });
85
+ };
39
86
  /**
40
87
  * Creates a new assistant with the provided data.
41
88
  *
@@ -45,8 +92,9 @@ var ACCOUNT_DOCTYPE = 'io.cozy.accounts';
45
92
  * @throws {Error} - Throws an error if the creation fails
46
93
  */
47
94
 
95
+
48
96
  var createAssistant = /*#__PURE__*/function () {
49
- var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(client, assistantData) {
97
+ var _ref3 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(client, assistantData) {
50
98
  var createdAccountId, account, response, assistant, _yield$client$save, savedAssistant;
51
99
 
52
100
  return _regenerator.default.wrap(function _callee$(_context) {
@@ -55,38 +103,21 @@ var createAssistant = /*#__PURE__*/function () {
55
103
  case 0:
56
104
  createdAccountId = null;
57
105
  _context.prev = 1;
58
- account = {
59
- _type: ACCOUNT_DOCTYPE,
60
- auth: {
61
- login: assistantData.model
62
- },
63
- account_type: assistantData.providerId
64
- };
65
-
66
- if (assistantData.baseUrl) {
67
- account.data = {
68
- baseUrl: assistantData.baseUrl
69
- };
70
- }
71
-
72
- if (assistantData.apiKey) {
73
- account.auth.password = assistantData.apiKey;
74
- }
75
-
76
- _context.next = 7;
106
+ account = buildProviderAccount(assistantData);
107
+ _context.next = 5;
77
108
  return client.save(account);
78
109
 
79
- case 7:
110
+ case 5:
80
111
  response = _context.sent;
81
112
 
82
113
  if (!(!response.data || !response.data._id)) {
83
- _context.next = 10;
114
+ _context.next = 8;
84
115
  break;
85
116
  }
86
117
 
87
118
  throw new Error('Failed to create account for assistant');
88
119
 
89
- case 10:
120
+ case 8:
90
121
  createdAccountId = response.data._id;
91
122
  assistant = {
92
123
  _type: ASSISTANT_DOCTYPE,
@@ -105,52 +136,52 @@ var createAssistant = /*#__PURE__*/function () {
105
136
  }
106
137
  }
107
138
  };
108
- _context.next = 14;
139
+ _context.next = 12;
109
140
  return client.save(assistant);
110
141
 
111
- case 14:
142
+ case 12:
112
143
  _yield$client$save = _context.sent;
113
144
  savedAssistant = _yield$client$save.data;
114
145
  return _context.abrupt("return", savedAssistant);
115
146
 
116
- case 19:
117
- _context.prev = 19;
147
+ case 17:
148
+ _context.prev = 17;
118
149
  _context.t0 = _context["catch"](1);
119
150
 
120
151
  if (!createdAccountId) {
121
- _context.next = 30;
152
+ _context.next = 28;
122
153
  break;
123
154
  }
124
155
 
125
- _context.prev = 22;
126
- _context.next = 25;
156
+ _context.prev = 20;
157
+ _context.next = 23;
127
158
  return client.stackClient.collection('io.cozy.accounts').destroy({
128
159
  _id: createdAccountId
129
160
  });
130
161
 
131
- case 25:
132
- _context.next = 30;
162
+ case 23:
163
+ _context.next = 28;
133
164
  break;
134
165
 
135
- case 27:
136
- _context.prev = 27;
137
- _context.t1 = _context["catch"](22);
166
+ case 25:
167
+ _context.prev = 25;
168
+ _context.t1 = _context["catch"](20);
138
169
 
139
170
  _logger.default.warn('Failed to cleanup orphaned account:', _context.t1);
140
171
 
141
- case 30:
172
+ case 28:
142
173
  throw new Error("Failed to create assistant: ".concat(_context.t0.message));
143
174
 
144
- case 31:
175
+ case 29:
145
176
  case "end":
146
177
  return _context.stop();
147
178
  }
148
179
  }
149
- }, _callee, null, [[1, 19], [22, 27]]);
180
+ }, _callee, null, [[1, 17], [20, 25]]);
150
181
  }));
151
182
 
152
183
  return function createAssistant(_x, _x2) {
153
- return _ref.apply(this, arguments);
184
+ return _ref3.apply(this, arguments);
154
185
  };
155
186
  }();
156
187
  /**
@@ -166,7 +197,7 @@ var createAssistant = /*#__PURE__*/function () {
166
197
  exports.createAssistant = createAssistant;
167
198
 
168
199
  var deleteAssistant = /*#__PURE__*/function () {
169
- var _ref2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(client, assistantId) {
200
+ var _ref4 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(client, assistantId) {
170
201
  var _yield$client$query, assistantDoc, included, provider;
171
202
 
172
203
  return _regenerator.default.wrap(function _callee2$(_context2) {
@@ -213,7 +244,7 @@ var deleteAssistant = /*#__PURE__*/function () {
213
244
  }));
214
245
 
215
246
  return function deleteAssistant(_x3, _x4) {
216
- return _ref2.apply(this, arguments);
247
+ return _ref4.apply(this, arguments);
217
248
  };
218
249
  }();
219
250
  /**
@@ -230,8 +261,8 @@ var deleteAssistant = /*#__PURE__*/function () {
230
261
  exports.deleteAssistant = deleteAssistant;
231
262
 
232
263
  var editAssistant = /*#__PURE__*/function () {
233
- var _ref3 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee3(client, assistantId, assistantData) {
234
- var _existedAssistant$inc, _existedAssistantData, _existedAssistantData2, _existedAssistantData3, _existedAssistantData4, _existedAssistantData5, existedAssistant, existedAssistantData, provider, account, _account$data, _account$auth, response, assistant;
264
+ var _ref5 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee3(client, assistantId, assistantData) {
265
+ var _existedAssistant$inc, _existedAssistantData, _existedAssistantData2, _existedAssistantData3, _existedAssistantData4, _existedAssistantData5, existedAssistant, existedAssistantData, provider, account, response, assistant;
235
266
 
236
267
  return _regenerator.default.wrap(function _callee3$(_context3) {
237
268
  while (1) {
@@ -263,41 +294,21 @@ var editAssistant = /*#__PURE__*/function () {
263
294
  throw new Error('Provider account not found for assistant');
264
295
 
265
296
  case 10:
266
- account = _objectSpread(_objectSpread({}, provider), {}, {
267
- auth: _objectSpread(_objectSpread({}, provider.auth || {}), {}, {
268
- login: assistantData.model
269
- }),
270
- account_type: assistantData.providerId,
271
- data: _objectSpread({}, provider.data || {})
272
- });
273
-
274
- if (assistantData.baseUrl) {
275
- account.data.baseUrl = assistantData.baseUrl;
276
- } else {
277
- (_account$data = account.data) === null || _account$data === void 0 ? true : delete _account$data.baseUrl;
278
- } // Only update the password if a new API key is explicitly provided
279
-
280
-
281
- if (assistantData.apiKey) {
282
- account.auth.password = assistantData.apiKey;
283
- } else if (!assistantData.baseUrl) {
284
- (_account$auth = account.auth) === null || _account$auth === void 0 ? true : delete _account$auth.password;
285
- }
286
-
287
- _context3.next = 15;
297
+ account = buildProviderAccount(assistantData, provider);
298
+ _context3.next = 13;
288
299
  return client.save(account);
289
300
 
290
- case 15:
301
+ case 13:
291
302
  response = _context3.sent;
292
303
 
293
304
  if (!(!response.data || !response.data._id)) {
294
- _context3.next = 18;
305
+ _context3.next = 16;
295
306
  break;
296
307
  }
297
308
 
298
309
  throw new Error('Failed to edit account for assistant');
299
310
 
300
- case 18:
311
+ case 16:
301
312
  assistant = _objectSpread(_objectSpread({}, existedAssistantData), {}, {
302
313
  name: assistantData.name,
303
314
  prompt: assistantData.prompt,
@@ -312,28 +323,28 @@ var editAssistant = /*#__PURE__*/function () {
312
323
  }
313
324
  }
314
325
  });
315
- _context3.next = 21;
326
+ _context3.next = 19;
316
327
  return client.save(assistant);
317
328
 
318
- case 21:
319
- _context3.next = 26;
329
+ case 19:
330
+ _context3.next = 24;
320
331
  break;
321
332
 
322
- case 23:
323
- _context3.prev = 23;
333
+ case 21:
334
+ _context3.prev = 21;
324
335
  _context3.t0 = _context3["catch"](0);
325
336
  throw new Error("Failed to edit assistant: ".concat(_context3.t0.message));
326
337
 
327
- case 26:
338
+ case 24:
328
339
  case "end":
329
340
  return _context3.stop();
330
341
  }
331
342
  }
332
- }, _callee3, null, [[0, 23]]);
343
+ }, _callee3, null, [[0, 21]]);
333
344
  }));
334
345
 
335
346
  return function editAssistant(_x5, _x6, _x7) {
336
- return _ref3.apply(this, arguments);
347
+ return _ref5.apply(this, arguments);
337
348
  };
338
349
  }();
339
350
 
@@ -0,0 +1,187 @@
1
+ import { createAssistant, editAssistant } from './assistant'
2
+
3
+ describe('assistant provider account', () => {
4
+ const assistantData = {
5
+ name: 'My agent',
6
+ prompt: 'Be helpful',
7
+ model: 'gemini-3-pro',
8
+ baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai/v1',
9
+ providerId: 'google',
10
+ providerName: 'Google'
11
+ }
12
+
13
+ describe('createAssistant', () => {
14
+ const buildClient = () => ({
15
+ save: jest
16
+ .fn()
17
+ .mockResolvedValue({ data: { _id: 'account-id', _type: 'whatever' } })
18
+ })
19
+
20
+ it('stores the model as configuration, not as a credential', async () => {
21
+ const client = buildClient()
22
+
23
+ await createAssistant(client, assistantData)
24
+
25
+ const account = client.save.mock.calls[0][0]
26
+ expect(account.data.model).toBe('gemini-3-pro')
27
+ // A model in `auth` would be duplicated into `credentials_encrypted`.
28
+ expect(account.auth.login).toBeUndefined()
29
+ })
30
+
31
+ it('keeps the model next to the other llm_override fields', async () => {
32
+ const client = buildClient()
33
+
34
+ await createAssistant(client, { ...assistantData, apiKey: 'sk-1234' })
35
+
36
+ const account = client.save.mock.calls[0][0]
37
+ expect(account.data).toEqual({
38
+ model: 'gemini-3-pro',
39
+ baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai/v1'
40
+ })
41
+ expect(account.auth.password).toBe('sk-1234')
42
+ })
43
+
44
+ it('labels the account after the provider', async () => {
45
+ const client = buildClient()
46
+
47
+ await createAssistant(client, assistantData)
48
+
49
+ const account = client.save.mock.calls[0][0]
50
+ expect(account.identifier).toBe('accountName')
51
+ expect(account.auth.accountName).toBe('Google')
52
+ // `name` is computed by the stack from `auth[identifier]` on creation
53
+ expect(account.name).toBeUndefined()
54
+ })
55
+
56
+ it('falls back to the provider id when no display name is given', async () => {
57
+ const client = buildClient()
58
+
59
+ await createAssistant(client, {
60
+ ...assistantData,
61
+ providerName: undefined
62
+ })
63
+
64
+ const account = client.save.mock.calls[0][0]
65
+ expect(account.auth.accountName).toBe('google')
66
+ })
67
+ })
68
+
69
+ describe('editAssistant', () => {
70
+ const buildClient = provider => ({
71
+ query: jest.fn().mockResolvedValue({
72
+ data: { _id: 'assistant-id', relationships: {} },
73
+ included: [provider]
74
+ }),
75
+ save: jest.fn().mockResolvedValue({ data: { _id: 'account-id' } })
76
+ })
77
+
78
+ it('migrates an account that still holds the model in auth.login', async () => {
79
+ const client = buildClient({
80
+ _id: 'account-id',
81
+ auth: {
82
+ login: 'gemini-2.5-flash',
83
+ credentials_encrypted: 'encrypted-blob'
84
+ }
85
+ })
86
+
87
+ await editAssistant(client, 'assistant-id', assistantData)
88
+
89
+ const account = client.save.mock.calls[0][0]
90
+ expect(account.data.model).toBe('gemini-3-pro')
91
+ expect(account.auth.login).toBeUndefined()
92
+ // Left alone: the stack reads the API key from it, never the model.
93
+ expect(account.auth.credentials_encrypted).toBe('encrypted-blob')
94
+ })
95
+
96
+ it('relabels an account created before the naming fix', async () => {
97
+ const client = buildClient({
98
+ _id: 'account-id',
99
+ auth: { login: 'gemini-2.5-flash' }
100
+ })
101
+
102
+ await editAssistant(client, 'assistant-id', assistantData)
103
+
104
+ const account = client.save.mock.calls[0][0]
105
+ expect(account.identifier).toBe('accountName')
106
+ expect(account.auth.accountName).toBe('Google')
107
+ })
108
+
109
+ // Pre-existing coupling, pinned because the builder now expresses it once.
110
+ it('replaces the stored API key when a new one is given', async () => {
111
+ const client = buildClient({
112
+ _id: 'account-id',
113
+ auth: { accountName: 'Google', password: 'sk-old' }
114
+ })
115
+
116
+ await editAssistant(client, 'assistant-id', {
117
+ ...assistantData,
118
+ apiKey: 'sk-new'
119
+ })
120
+
121
+ expect(client.save.mock.calls[0][0].auth.password).toBe('sk-new')
122
+ })
123
+
124
+ it('keeps the stored API key when only a baseUrl is given', async () => {
125
+ const client = buildClient({
126
+ _id: 'account-id',
127
+ auth: { accountName: 'Google', password: 'sk-old' }
128
+ })
129
+
130
+ await editAssistant(client, 'assistant-id', {
131
+ ...assistantData,
132
+ apiKey: undefined
133
+ })
134
+
135
+ expect(client.save.mock.calls[0][0].auth.password).toBe('sk-old')
136
+ })
137
+
138
+ it('drops the stored API key when neither key nor baseUrl is given', async () => {
139
+ const client = buildClient({
140
+ _id: 'account-id',
141
+ auth: { accountName: 'Google', password: 'sk-old' }
142
+ })
143
+
144
+ await editAssistant(client, 'assistant-id', {
145
+ ...assistantData,
146
+ apiKey: undefined,
147
+ baseUrl: undefined
148
+ })
149
+
150
+ const account = client.save.mock.calls[0][0]
151
+ expect(account.auth.password).toBeUndefined()
152
+ expect(account.data.baseUrl).toBeUndefined()
153
+ })
154
+
155
+ it('preserves the fields it does not own', async () => {
156
+ const client = buildClient({
157
+ _id: 'account-id',
158
+ _rev: '3-abc',
159
+ name: 'Google',
160
+ cozyMetadata: { createdAt: 'yesterday' },
161
+ auth: { accountName: 'Google', credentials_encrypted: 'blob' }
162
+ })
163
+
164
+ await editAssistant(client, 'assistant-id', assistantData)
165
+
166
+ const account = client.save.mock.calls[0][0]
167
+ expect(account._rev).toBe('3-abc')
168
+ expect(account.cozyMetadata).toEqual({ createdAt: 'yesterday' })
169
+ expect(account.auth.credentials_encrypted).toBe('blob')
170
+ })
171
+
172
+ it('updates the model of an already migrated account', async () => {
173
+ const client = buildClient({
174
+ _id: 'account-id',
175
+ identifier: 'accountName',
176
+ auth: { accountName: 'Google' },
177
+ data: { model: 'gemini-2.5-flash', baseUrl: 'https://example.org' }
178
+ })
179
+
180
+ await editAssistant(client, 'assistant-id', assistantData)
181
+
182
+ const account = client.save.mock.calls[0][0]
183
+ expect(account.data.model).toBe('gemini-3-pro')
184
+ expect(account.data.baseUrl).toBe(assistantData.baseUrl)
185
+ })
186
+ })
187
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cozy-client",
3
- "version": "60.30.1",
3
+ "version": "60.30.6",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.node.js",
6
6
  "browser": "dist/index.js",
@@ -74,5 +74,5 @@
74
74
  "react-native-ios11-devicecheck": "^0.0.3"
75
75
  },
76
76
  "sideEffects": false,
77
- "gitHead": "f209689b3951ddbedc83db50a2dcd976082777b3"
77
+ "gitHead": "88101a3d13943828cd1cfcf39c40535c0a259afa"
78
78
  }
@@ -595,7 +595,7 @@ declare class CozyClient {
595
595
  * Resolves relationships on a document.
596
596
  *
597
597
  * The original document is kept in the target attribute of
598
- * the relationship
598
+ * the relationship.
599
599
  *
600
600
  * @param {import("./types").CozyClientDocument} document - for which relationships must be resolved
601
601
  * @param {Schema} [schemaArg] - The schema describing the relationships
@@ -30,5 +30,10 @@ export type Assistant = {
30
30
  * - ID of the provider
31
31
  */
32
32
  providerId: string;
33
+ /**
34
+ * - Display name of the provider, used to
35
+ * name the account. Defaults to the provider id.
36
+ */
37
+ providerName?: string;
33
38
  };
34
39
  import CozyClient from "../CozyClient";