@kiipu/cli 0.0.7 → 0.0.9

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,6 +1,6 @@
1
- import { formatPostCollection, formatPostDetail } from '../lib/post-formatters.js';
1
+ import { formatNoteCollection, formatNoteDetail } from '../lib/note-formatters.js';
2
2
  import { KiipuUserApiClient } from '../lib/kiipu-user-client.js';
3
- import { executePostAction } from '../lib/post-actions.js';
3
+ import { executeNoteAction } from '../lib/note-actions.js';
4
4
  import { hasFlag, readFlag } from '../utils/args.js';
5
5
  const actions = new Set([
6
6
  'create',
@@ -18,21 +18,21 @@ const sortValues = new Set(['updatedAt', 'createdAt', 'title']);
18
18
  function usage(action) {
19
19
  switch (action) {
20
20
  case 'create':
21
- return 'Usage: kiipu post create --content "<text>"\n or: kiipu post create "<text>"';
21
+ return 'Usage: kiipu note create --content "<text>"\n or: kiipu note create "<text>"';
22
22
  case 'list':
23
- return 'Usage: kiipu post list [--tag <tag>] [--sort <updatedAt|createdAt|title>] [--starred] [--deleted]';
23
+ return 'Usage: kiipu note list [--tag <tag>] [--sort <updatedAt|createdAt|title>] [--starred] [--deleted]';
24
24
  case 'search':
25
- return 'Usage: kiipu post search <query>\n or: kiipu post search --query "<query>"';
25
+ return 'Usage: kiipu note search <query>\n or: kiipu note search --query "<query>"';
26
26
  case 'show':
27
- return 'Usage: kiipu post show --id <postId>';
27
+ return 'Usage: kiipu note show --id <noteId>';
28
28
  case 'update':
29
- return 'Usage: kiipu post update --id <postId> --content "<text>" [--title "<title>"] [--visibility public|private] [--tags <a,b,c>]';
29
+ return 'Usage: kiipu note update --id <noteId> --content "<text>" [--title "<title>"] [--visibility public|private] [--tags <a,b,c>]';
30
30
  case 'star':
31
- return 'Usage: kiipu post star --id <postId>';
31
+ return 'Usage: kiipu note star --id <noteId>';
32
32
  case 'pin':
33
- return 'Usage: kiipu post pin --id <postId>';
33
+ return 'Usage: kiipu note pin --id <noteId>';
34
34
  default:
35
- return `Usage: kiipu post ${action} --id <postId>`;
35
+ return `Usage: kiipu note ${action} --id <noteId>`;
36
36
  }
37
37
  }
38
38
  function error(message) {
@@ -81,9 +81,9 @@ function validateSort(sort) {
81
81
  }
82
82
  return sortValues.has(sort) ? sort : null;
83
83
  }
84
- function parsePostId(args, action) {
85
- const postId = readFlag(args, '--id')?.trim();
86
- return postId ? postId : error(usage(action));
84
+ function parseNoteId(args, action) {
85
+ const noteId = readFlag(args, '--id')?.trim();
86
+ return noteId ? noteId : error(usage(action));
87
87
  }
88
88
  function parseTags(input) {
89
89
  if (!input) {
@@ -105,19 +105,19 @@ function parseTags(input) {
105
105
  }
106
106
  return Array.from(unique.values());
107
107
  }
108
- function toCliPost(post) {
108
+ function toCliNote(note) {
109
109
  return {
110
- id: post.id,
111
- title: post.title,
112
- rawText: post.rawText,
113
- finalText: post.finalText,
114
- visibility: post.visibility,
115
- tags: post.tags,
116
- folder: post.folder ?? null,
117
- isPinned: post.isPinned,
118
- isStarred: post.isStarred,
119
- createdAt: post.createdAt,
120
- updatedAt: post.updatedAt,
110
+ id: note.id,
111
+ title: note.title,
112
+ rawText: note.rawText,
113
+ finalText: note.finalText,
114
+ visibility: note.visibility,
115
+ tags: note.tags,
116
+ folder: note.folder ?? null,
117
+ isPinned: note.isPinned,
118
+ isStarred: note.isStarred,
119
+ createdAt: note.createdAt,
120
+ updatedAt: note.updatedAt,
121
121
  };
122
122
  }
123
123
  async function handleMutationAction(config, action, args) {
@@ -126,13 +126,13 @@ async function handleMutationAction(config, action, args) {
126
126
  if (!content) {
127
127
  return error(usage('create'));
128
128
  }
129
- return executePostAction(config, { action: 'create', content });
129
+ return executeNoteAction(config, { action: 'create', content });
130
130
  }
131
- const postId = parsePostId(args, action);
132
- if (typeof postId !== 'string') {
133
- return postId;
131
+ const noteId = parseNoteId(args, action);
132
+ if (typeof noteId !== 'string') {
133
+ return noteId;
134
134
  }
135
- return executePostAction(config, { action, postId });
135
+ return executeNoteAction(config, { action, noteId });
136
136
  }
137
137
  async function handleList(config, args) {
138
138
  const { client, error: clientError } = requireUserClient(config);
@@ -149,36 +149,36 @@ async function handleList(config, args) {
149
149
  return error(`--starred and --deleted cannot be used together.\n${usage('list')}`);
150
150
  }
151
151
  const tag = readFlag(args, '--tag');
152
- let posts;
152
+ let notes;
153
153
  let responseData;
154
154
  if (starred) {
155
- const response = await client.listStarredPosts({ tag, sort });
155
+ const response = await client.listStarredNotes({ tag, sort });
156
156
  if (!response.ok) {
157
157
  return error(response.error.message);
158
158
  }
159
- posts = response.data.map((entry) => toCliPost(entry.post));
159
+ notes = response.data.map((entry) => toCliNote(entry.note));
160
160
  responseData = response.data;
161
161
  }
162
162
  else if (deleted) {
163
- const response = await client.listDeletedPosts({ sort });
163
+ const response = await client.listDeletedNotes({ sort });
164
164
  if (!response.ok) {
165
165
  return error(response.error.message);
166
166
  }
167
- posts = response.data.map(toCliPost);
167
+ notes = response.data.map(toCliNote);
168
168
  responseData = response.data;
169
169
  }
170
170
  else {
171
- const response = await client.listPosts({ tag, sort });
171
+ const response = await client.listNotes({ tag, sort });
172
172
  if (!response.ok) {
173
173
  return error(response.error.message);
174
174
  }
175
- posts = response.data.map(toCliPost);
175
+ notes = response.data.map(toCliNote);
176
176
  responseData = response.data;
177
177
  }
178
- const title = starred ? 'Starred posts' : deleted ? 'Deleted posts' : 'Posts';
178
+ const title = starred ? 'Starred notes' : deleted ? 'Deleted notes' : 'Notes';
179
179
  return {
180
180
  ok: true,
181
- message: formatPostCollection(title, posts),
181
+ message: formatNoteCollection(title, notes),
182
182
  data: responseData,
183
183
  };
184
184
  }
@@ -191,13 +191,13 @@ async function handleSearch(config, args) {
191
191
  if (!query) {
192
192
  return error(usage('search'));
193
193
  }
194
- const response = await client.searchPosts(query);
194
+ const response = await client.searchNotes(query);
195
195
  if (!response.ok) {
196
196
  return error(response.error.message);
197
197
  }
198
198
  return {
199
199
  ok: true,
200
- message: formatPostCollection(`Search results for "${query}"`, response.data.map(toCliPost)),
200
+ message: formatNoteCollection(`Search results for "${query}"`, response.data.map(toCliNote)),
201
201
  data: response.data,
202
202
  };
203
203
  }
@@ -206,17 +206,17 @@ async function handleShow(config, args) {
206
206
  if (!client) {
207
207
  return clientError;
208
208
  }
209
- const postId = parsePostId(args, 'show');
210
- if (typeof postId !== 'string') {
211
- return postId;
209
+ const noteId = parseNoteId(args, 'show');
210
+ if (typeof noteId !== 'string') {
211
+ return noteId;
212
212
  }
213
- const response = await client.getPost(postId);
213
+ const response = await client.getNote(noteId);
214
214
  if (!response.ok) {
215
215
  return error(response.error.message);
216
216
  }
217
217
  return {
218
218
  ok: true,
219
- message: formatPostDetail(toCliPost(response.data)),
219
+ message: formatNoteDetail(toCliNote(response.data)),
220
220
  data: response.data,
221
221
  };
222
222
  }
@@ -225,9 +225,9 @@ async function handleUpdate(config, args) {
225
225
  if (!client) {
226
226
  return clientError;
227
227
  }
228
- const postId = parsePostId(args, 'update');
229
- if (typeof postId !== 'string') {
230
- return postId;
228
+ const noteId = parseNoteId(args, 'update');
229
+ if (typeof noteId !== 'string') {
230
+ return noteId;
231
231
  }
232
232
  const content = readFlag(args, '--content')?.trim();
233
233
  if (!content) {
@@ -239,7 +239,7 @@ async function handleUpdate(config, args) {
239
239
  }
240
240
  const title = readFlag(args, '--title');
241
241
  const tags = readFlag(args, '--tags');
242
- const response = await client.updatePost(postId, {
242
+ const response = await client.updateNote(noteId, {
243
243
  rawText: content,
244
244
  ...(title !== undefined ? { title: title || null } : {}),
245
245
  ...(visibility === 'public' || visibility === 'private' ? { visibility } : {}),
@@ -250,7 +250,7 @@ async function handleUpdate(config, args) {
250
250
  }
251
251
  return {
252
252
  ok: true,
253
- message: `Post updated.\n\n${formatPostDetail(toCliPost(response.data))}`,
253
+ message: `Note updated.\n\n${formatNoteDetail(toCliNote(response.data))}`,
254
254
  data: response.data,
255
255
  };
256
256
  }
@@ -259,17 +259,19 @@ async function handleStar(config, args) {
259
259
  if (!client) {
260
260
  return clientError;
261
261
  }
262
- const postId = parsePostId(args, 'star');
263
- if (typeof postId !== 'string') {
264
- return postId;
262
+ const noteId = parseNoteId(args, 'star');
263
+ if (typeof noteId !== 'string') {
264
+ return noteId;
265
265
  }
266
- const response = await client.toggleStar(postId);
266
+ const response = await client.toggleStar(noteId);
267
267
  if (!response.ok) {
268
268
  return error(response.error.message);
269
269
  }
270
270
  return {
271
271
  ok: true,
272
- message: response.data.isStarred ? `Post starred. ${response.data.id}` : `Post unstarred. ${response.data.id}`,
272
+ message: response.data.isStarred
273
+ ? `Note starred. ${response.data.id}`
274
+ : `Note unstarred. ${response.data.id}`,
273
275
  data: response.data,
274
276
  };
275
277
  }
@@ -278,24 +280,26 @@ async function handlePin(config, args) {
278
280
  if (!client) {
279
281
  return clientError;
280
282
  }
281
- const postId = parsePostId(args, 'pin');
282
- if (typeof postId !== 'string') {
283
- return postId;
283
+ const noteId = parseNoteId(args, 'pin');
284
+ if (typeof noteId !== 'string') {
285
+ return noteId;
284
286
  }
285
- const response = await client.togglePin(postId);
287
+ const response = await client.togglePin(noteId);
286
288
  if (!response.ok) {
287
289
  return error(response.error.message);
288
290
  }
289
291
  return {
290
292
  ok: true,
291
- message: response.data.isPinned ? `Post pinned. ${response.data.id}` : `Post unpinned. ${response.data.id}`,
293
+ message: response.data.isPinned
294
+ ? `Note pinned. ${response.data.id}`
295
+ : `Note unpinned. ${response.data.id}`,
292
296
  data: response.data,
293
297
  };
294
298
  }
295
- export async function runPostCommand(config, args) {
299
+ export async function runNoteCommand(config, args) {
296
300
  const action = args[1];
297
301
  if (!action || !actions.has(action)) {
298
- return error('Usage: kiipu post <create|delete|restore|purge|list|search|show|update|star|pin> [options]');
302
+ return error('Usage: kiipu note <create|delete|restore|purge|list|search|show|update|star|pin> [options]');
299
303
  }
300
304
  const actionArgs = args.slice(2);
301
305
  switch (action) {
@@ -317,5 +321,5 @@ export async function runPostCommand(config, args) {
317
321
  case 'pin':
318
322
  return handlePin(config, actionArgs);
319
323
  }
320
- return error('Unsupported post action.');
324
+ return error('Unsupported note action.');
321
325
  }
@@ -13,7 +13,10 @@ function parseEnvFile(content) {
13
13
  if (!key || process.env[key] !== undefined) {
14
14
  continue;
15
15
  }
16
- const value = line.slice(separatorIndex + 1).trim().replace(/^['"]|['"]$/g, '');
16
+ const value = line
17
+ .slice(separatorIndex + 1)
18
+ .trim()
19
+ .replace(/^['"]|['"]$/g, '');
17
20
  process.env[key] = value;
18
21
  }
19
22
  }
package/dist/index.js CHANGED
@@ -1,16 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import './config/load-env.js';
3
3
  import { createDefaultConfig, loadKiipuConfig } from './config/config.js';
4
+ import { runAskCommand } from './commands/ask.js';
4
5
  import { runAuthCommand } from './commands/auth.js';
5
6
  import { runDoctorCommand } from './commands/doctor.js';
6
7
  import { getHelpResult } from './commands/help.js';
7
- import { runPostCommand } from './commands/post.js';
8
+ import { runNoteCommand } from './commands/note.js';
8
9
  import { runSkillsCommand } from './commands/skills.js';
9
10
  import { readFlag, hasFlag } from './utils/args.js';
10
11
  import { CLI_VERSION } from './version.js';
11
12
  function printResult(result, asJson) {
12
13
  if (asJson) {
13
- console.log(JSON.stringify(result, null, 2));
14
+ console.log(JSON.stringify(result.json ?? result, null, 2));
14
15
  }
15
16
  else {
16
17
  console.log(result.message);
@@ -44,6 +45,11 @@ async function main() {
44
45
  '--title',
45
46
  '--visibility',
46
47
  '--tags',
48
+ '--question',
49
+ '--conversation-id',
50
+ '--top-k',
51
+ '--source-mode',
52
+ '--limit',
47
53
  ]);
48
54
  const positionalArgs = normalizedArgs.filter((arg, index, all) => {
49
55
  if (arg === '--json' ||
@@ -80,8 +86,15 @@ async function main() {
80
86
  result = await runDoctorCommand(await loadKiipuConfig());
81
87
  return printResult(result, asJson);
82
88
  }
83
- if (command === 'post') {
84
- result = await runPostCommand(config, commandArgs);
89
+ if (command === 'note') {
90
+ result = await runNoteCommand(config, commandArgs);
91
+ return printResult(result, asJson);
92
+ }
93
+ if (command === 'ask') {
94
+ result = await runAskCommand(config, commandArgs, {
95
+ stream: !asJson,
96
+ write: (chunk) => process.stdout.write(chunk),
97
+ });
85
98
  return printResult(result, asJson);
86
99
  }
87
100
  if (command === 'auth') {
@@ -0,0 +1,278 @@
1
+ import { TextDecoder } from 'node:util';
2
+ function isRecord(input) {
3
+ return typeof input === 'object' && input !== null;
4
+ }
5
+ function buildError(message, code = 'request_failed') {
6
+ return {
7
+ ok: false,
8
+ error: {
9
+ code,
10
+ message,
11
+ },
12
+ };
13
+ }
14
+ function getErrorMessage(payload, fallback) {
15
+ if (!isRecord(payload)) {
16
+ return fallback;
17
+ }
18
+ if (typeof payload.message === 'string') {
19
+ return payload.message;
20
+ }
21
+ if (isRecord(payload.message) && typeof payload.message.message === 'string') {
22
+ return payload.message.message;
23
+ }
24
+ if (isRecord(payload.error) && typeof payload.error.message === 'string') {
25
+ return payload.error.message;
26
+ }
27
+ return fallback;
28
+ }
29
+ function getErrorCode(payload, fallback = 'request_failed') {
30
+ if (!isRecord(payload)) {
31
+ return fallback;
32
+ }
33
+ if (typeof payload.code === 'string') {
34
+ return payload.code;
35
+ }
36
+ if (isRecord(payload.message) && typeof payload.message.code === 'string') {
37
+ return payload.message.code;
38
+ }
39
+ if (isRecord(payload.error) && typeof payload.error.code === 'string') {
40
+ return payload.error.code;
41
+ }
42
+ return fallback;
43
+ }
44
+ function parseSource(input) {
45
+ if (!isRecord(input)) {
46
+ return null;
47
+ }
48
+ const noteId = typeof input.noteId === 'string' ? input.noteId : '';
49
+ const snippet = typeof input.snippet === 'string' ? input.snippet : '';
50
+ if (!noteId || !snippet) {
51
+ return null;
52
+ }
53
+ return {
54
+ index: typeof input.index === 'number' ? input.index : 0,
55
+ noteId,
56
+ title: typeof input.title === 'string' ? input.title : null,
57
+ snippet,
58
+ score: typeof input.score === 'number' ? input.score : 0,
59
+ createdAt: typeof input.createdAt === 'string' ? input.createdAt : undefined,
60
+ };
61
+ }
62
+ function parseSources(input) {
63
+ return Array.isArray(input)
64
+ ? input.map(parseSource).filter((source) => Boolean(source))
65
+ : [];
66
+ }
67
+ function parseAskEvent(input) {
68
+ if (!isRecord(input) || typeof input.type !== 'string') {
69
+ return null;
70
+ }
71
+ switch (input.type) {
72
+ case 'meta':
73
+ if (typeof input.conversationId !== 'string') {
74
+ return null;
75
+ }
76
+ return {
77
+ type: 'meta',
78
+ conversationId: input.conversationId,
79
+ isNew: Boolean(input.isNew),
80
+ title: typeof input.title === 'string' ? input.title : null,
81
+ };
82
+ case 'sources':
83
+ return {
84
+ type: 'sources',
85
+ sources: parseSources(input.sources),
86
+ locked: Boolean(input.locked),
87
+ retrievalMode: input.retrievalMode === 'semantic' || input.retrievalMode === 'temporal_list'
88
+ ? input.retrievalMode
89
+ : undefined,
90
+ totalCount: typeof input.totalCount === 'number' ? input.totalCount : undefined,
91
+ truncated: typeof input.truncated === 'boolean' ? input.truncated : undefined,
92
+ };
93
+ case 'delta':
94
+ return typeof input.text === 'string' ? { type: 'delta', text: input.text } : null;
95
+ case 'done':
96
+ if (typeof input.turnId !== 'string') {
97
+ return null;
98
+ }
99
+ return {
100
+ type: 'done',
101
+ turnId: input.turnId,
102
+ inputTokens: typeof input.inputTokens === 'number' ? input.inputTokens : 0,
103
+ outputTokens: typeof input.outputTokens === 'number' ? input.outputTokens : 0,
104
+ latencyMs: typeof input.latencyMs === 'number' ? input.latencyMs : 0,
105
+ };
106
+ case 'title':
107
+ if (typeof input.conversationId !== 'string' || typeof input.title !== 'string') {
108
+ return null;
109
+ }
110
+ return { type: 'title', conversationId: input.conversationId, title: input.title };
111
+ case 'error':
112
+ return {
113
+ type: 'error',
114
+ code: typeof input.code === 'string' ? input.code : 'unknown',
115
+ message: typeof input.message === 'string' ? input.message : 'Unknown Ask error.',
116
+ };
117
+ default:
118
+ return null;
119
+ }
120
+ }
121
+ function extractDataField(frame) {
122
+ const dataLines = [];
123
+ for (const line of frame.split('\n')) {
124
+ const normalized = line.endsWith('\r') ? line.slice(0, -1) : line;
125
+ if (normalized.startsWith('data:')) {
126
+ dataLines.push(normalized.slice(5).replace(/^ /, ''));
127
+ }
128
+ }
129
+ return dataLines.length > 0 ? dataLines.join('\n') : null;
130
+ }
131
+ async function readJson(response) {
132
+ try {
133
+ return await response.json();
134
+ }
135
+ catch {
136
+ return null;
137
+ }
138
+ }
139
+ export class KiipuAskClient {
140
+ config;
141
+ constructor(config) {
142
+ this.config = config;
143
+ }
144
+ async requestJson(path, init) {
145
+ let response;
146
+ try {
147
+ response = await fetch(`${this.config.apiBaseUrl}${path}`, {
148
+ ...init,
149
+ headers: {
150
+ 'Content-Type': 'application/json',
151
+ Authorization: `Bearer ${this.config.apiKey}`,
152
+ ...(init.headers ?? {}),
153
+ },
154
+ });
155
+ }
156
+ catch {
157
+ return buildError(`Kiipu API is unreachable at ${this.config.apiBaseUrl}.`, 'api_unreachable');
158
+ }
159
+ const payload = await readJson(response);
160
+ if (!response.ok) {
161
+ return buildError(getErrorMessage(payload, `Request failed with ${response.status}.`), getErrorCode(payload, `http_${response.status}`));
162
+ }
163
+ const data = isRecord(payload) && 'data' in payload ? payload.data : payload;
164
+ return {
165
+ ok: true,
166
+ data: data,
167
+ };
168
+ }
169
+ listConversations(input) {
170
+ const search = new URLSearchParams();
171
+ if (input.query) {
172
+ search.set('q', input.query);
173
+ }
174
+ if (input.limit) {
175
+ search.set('limit', String(input.limit));
176
+ }
177
+ if (input.archived) {
178
+ search.set('view', 'archived');
179
+ }
180
+ const query = search.toString();
181
+ return this.requestJson(`/ai/conversations${query ? `?${query}` : ''}`, { method: 'GET' });
182
+ }
183
+ getConversation(id) {
184
+ return this.requestJson(`/ai/conversations/${encodeURIComponent(id)}`, {
185
+ method: 'GET',
186
+ });
187
+ }
188
+ async *streamMessage(input) {
189
+ const body = {
190
+ question: input.question,
191
+ ...(input.topK ? { topK: input.topK } : {}),
192
+ ...(input.sourceMode ? { sourceMode: input.sourceMode } : {}),
193
+ };
194
+ let response;
195
+ try {
196
+ response = await fetch(`${this.config.apiBaseUrl}/ai/conversations/${encodeURIComponent(input.conversationId)}/messages`, {
197
+ method: 'POST',
198
+ headers: {
199
+ 'Content-Type': 'application/json',
200
+ Authorization: `Bearer ${this.config.apiKey}`,
201
+ Accept: 'text/event-stream',
202
+ },
203
+ body: JSON.stringify(body),
204
+ });
205
+ }
206
+ catch (error) {
207
+ yield {
208
+ type: 'error',
209
+ code: 'network_error',
210
+ message: error instanceof Error ? error.message : 'Network request failed.',
211
+ };
212
+ return;
213
+ }
214
+ if (!response.ok || !response.body) {
215
+ const payload = await readJson(response);
216
+ yield {
217
+ type: 'error',
218
+ code: getErrorCode(payload, `http_${response.status}`),
219
+ message: getErrorMessage(payload, `Request failed with ${response.status}.`),
220
+ };
221
+ return;
222
+ }
223
+ let settled = false;
224
+ const decoder = new TextDecoder();
225
+ const reader = response.body.getReader();
226
+ let buffer = '';
227
+ try {
228
+ while (true) {
229
+ const { value, done } = await reader.read();
230
+ if (done) {
231
+ break;
232
+ }
233
+ buffer += decoder.decode(value, { stream: true });
234
+ let separator = buffer.indexOf('\n\n');
235
+ while (separator !== -1) {
236
+ const frame = buffer.slice(0, separator);
237
+ buffer = buffer.slice(separator + 2);
238
+ const data = extractDataField(frame);
239
+ if (data && data !== '[DONE]') {
240
+ try {
241
+ const event = parseAskEvent(JSON.parse(data));
242
+ if (event) {
243
+ if (event.type === 'done' || event.type === 'error') {
244
+ settled = true;
245
+ }
246
+ yield event;
247
+ }
248
+ }
249
+ catch {
250
+ // Ignore malformed SSE frames from intermediaries.
251
+ }
252
+ }
253
+ separator = buffer.indexOf('\n\n');
254
+ }
255
+ }
256
+ }
257
+ catch (error) {
258
+ if (!settled) {
259
+ yield {
260
+ type: 'error',
261
+ code: 'stream_error',
262
+ message: error instanceof Error ? error.message : 'Stream interrupted.',
263
+ };
264
+ }
265
+ return;
266
+ }
267
+ finally {
268
+ reader.releaseLock();
269
+ }
270
+ if (!settled) {
271
+ yield {
272
+ type: 'error',
273
+ code: 'stream_truncated',
274
+ message: 'The response ended unexpectedly. Please try again.',
275
+ };
276
+ }
277
+ }
278
+ }