@ykurilov/agent-chat-server 0.86.0 → 0.87.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/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # @ykurilov/agent-chat-server
2
2
 
3
+ ## 0.87.0
4
+
5
+ ### Minor Changes
6
+
7
+ - eec4550: Внешний исполнитель принимает вложения и даёт выбирать свою модель. Swift-клиент сохраняет файлы до отправки, восстанавливает загрузку после перезапуска; «Одуванчик» показывает пикеры фото/камеры/файлов, карточки и подтверждение смены модели. Произвольные файлы и ZIP включаются отдельно, без распаковки. Сбой сверки сам по себе больше не объявляет исполнителя offline.
8
+
9
+ ### Как обновить приложение
10
+
11
+ 1. Обновите все установленные пакеты SDK одной версией и Swift SPM-тег до 0.87.0. Существующий текстовый API не меняется. Новых обязательных env и SQL-миграций нет.
12
+ 2. Для файлов создайте `createExternalAttachments({storage,allowGenericFiles:true})` из `@ykurilov/agent-chat-attachments` и передайте один объект в `attachments` сервиса и HTTP-обработчика внешних бесед. Используйте постоянное AttachmentStorage и задайте ограничения приложения. Если собственное хранилище или код исчерпывающе перечисляет AttachmentKind, добавьте новый `file`; этот вид используется для opt-in произвольных файлов. Адаптер реализует необязательный `sendMessage({text,messageId,attachments})`, каждый файл имеет `readBytes()`. Текст бывает пустым. Текстовый replay того же ID сохраняет файлы SDK.
13
+ 3. В Swift готовый экран принимает `attachmentsEnabled:true`; свой экран использует `AgentChatAttachmentController`, `AgentChatComposer(attachmentController:)` и `AgentChatMessageAttachments`. При отправке передайте `attachmentIds:controller.readyAttachmentIds` в `client.send`. Для камеры добавьте `NSCameraUsageDescription`. Фото преобразуются в JPEG до 2048 px и base64 до 5 МиБ.
14
+ 4. Для выбора модели адаптер реализует `models()` и `setModel({commandId,expectedRevision,choice})`, передаёт изменения событием `model_state`. Каталог, текущий выбор и revision исходят от исполнителя. После неоднозначной ошибки команда не повторяется автоматически. Swift скрывает выбор reasoning по умолчанию; включение отдельное. Точный контракт и машинные коды - `docs/external-native.md`.
15
+ 5. Собственный ConversationStore должен сохранять необязательные `messages[].attachments`, `modelState`, `modelCommands` атомарно с остальной записью. Встроенный SQLite готов. Обновите Swift-клиент до включения новых событий. Перед закрытием адаптера/хранилища дождитесь `service.close()`.
16
+ 6. Проверьте сообщение только с файлом, повторное подключение без дублей, загрузку после перезапуска и смену модели с подтверждением исполнителя. Запрет смены во время хода должен вернуть машинный busy; файлы другого владельца - 404.
17
+
18
+ ### Patch Changes
19
+
20
+ - Updated dependencies [eec4550]
21
+ - @ykurilov/agent-chat-core@0.87.0
22
+
23
+ ## 0.86.1
24
+
25
+ ### Patch Changes
26
+
27
+ - Updated dependencies [f23e7bc]
28
+ - @ykurilov/agent-chat-core@0.86.1
29
+
3
30
  ## 0.86.0
4
31
 
5
32
  ### Patch Changes
package/dist/external.js CHANGED
@@ -15,9 +15,21 @@ function createExternalChatHandler(options) {
15
15
  if (!user) return json({ error: { kind: "unauthorized" } }, 401);
16
16
  const parts = url.pathname.slice(base.length).split("/").filter(Boolean).map(decodeURIComponent);
17
17
  if (parts.length === 0 && request.method === "GET") return json({ version: 1, conversations: await service.list(user.id) });
18
+ if (parts.length === 2 && parts[0] === "attachments" && parts[1] === "settings" && request.method === "GET") return options.attachments?.settings() ?? json({ error: { kind: "not_found" } }, 404);
18
19
  const id = parts[0];
19
20
  if (!id) return json({ error: { kind: "not_found" } }, 404);
20
21
  await service.snapshot(user.id, id);
22
+ if (parts[1] === "attachments") return options.attachments?.handle({ request, userId: user.id, chatId: id, segments: parts.slice(2) }) ?? json({ error: { kind: "not_found" } }, 404);
23
+ if (parts[1] === "model" && parts.length === 2) {
24
+ if (request.method === "GET") {
25
+ await service.start(user.id, id);
26
+ return json({ version: 1, modelState: (await service.snapshot(user.id, id)).modelState ?? null });
27
+ }
28
+ if (request.method === "PUT") {
29
+ const body = JSON.parse(await readBody(request));
30
+ return json({ version: 1, modelState: await service.setModel(user.id, id, body) });
31
+ }
32
+ }
21
33
  if (parts.length === 1 && request.method === "GET") return json(await service.snapshot(user.id, id));
22
34
  if (parts[1] === "events" && parts.length === 2 && request.method === "GET") {
23
35
  const raw = url.searchParams.get("after") ?? request.headers.get("last-event-id") ?? "0";
@@ -93,7 +105,7 @@ data: ${JSON.stringify(event)}
93
105
  if (!body || typeof body !== "object" || Array.isArray(body)) throw Error("invalid_body");
94
106
  if (parts[1] === "messages" && parts.length === 2) {
95
107
  if (typeof body.clientMessageId !== "string" || typeof body.text !== "string") throw Error("invalid_message");
96
- const receipt = await service.send(user.id, id, { clientMessageId: body.clientMessageId, text: body.text });
108
+ const receipt = await service.send(user.id, id, { clientMessageId: body.clientMessageId, text: body.text, ...body.attachmentIds !== void 0 && { attachmentIds: body.attachmentIds } });
97
109
  return json({ version: 1, ...receipt }, 202);
98
110
  }
99
111
  if (parts[1] === "stop" && parts.length === 2) {
@@ -115,6 +127,9 @@ data: ${JSON.stringify(event)}
115
127
  if (kind === "not_found") return json({ error: { kind } }, 404);
116
128
  if (kind === "body_too_large") return json({ error: { kind } }, 413);
117
129
  if (["busy", "message_id_conflict", "command_id_conflict", "permission_not_pending", "executor_offline", "executor_not_supported"].includes(kind)) return json({ error: { kind, ...kind === "busy" && { code: "external_conversation_busy" } } }, 409);
130
+ const code = error && typeof error === "object" && "code" in error && typeof error.code === "string" ? error.code : kind;
131
+ if (code.startsWith("external_model_") || kind === "external_attachments_unsupported") return json({ error: { kind: code, code } }, 409);
132
+ if (kind.startsWith("attachment_")) return json({ error: { kind } }, kind === "attachment_not_found" ? 404 : 422);
118
133
  if (kind.startsWith("invalid_") || error instanceof SyntaxError || error instanceof URIError) return json({ error: { kind: "invalid" } }, 400);
119
134
  options.logError?.(error);
120
135
  return json({ error: { kind: "internal" } }, 500);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/external.ts"],"sourcesContent":["import type { ExternalConversationService, PermissionDecision } from \"@ykurilov/agent-chat-core/external\";\nimport { createRequestOriginCheck } from \"@ykurilov/agent-chat-core\";\n\nexport interface ExternalChatHandlerOptions {\n service:ExternalConversationService;\n basePath?:string;\n resolveUser:(request:Request)=>{id:string}|null|Promise<{id:string}|null>;\n allowedOrigins?:string[];\n pollMs?:number; heartbeatMs?:number;\n logError?:(error:unknown)=>void;\n}\n/** Version 1 native protocol. Standard fetch handler: mount with Hono's app.all(..., c => handler.fetch(c.req.raw)). */\nexport function createExternalChatHandler(options:ExternalChatHandlerOptions) {\n const base=(options.basePath??\"/api/conversations\").replace(/\\/$/,\"\");\n const originCheck=createRequestOriginCheck({allowedOrigins:options.allowedOrigins});\n const service=options.service;\n const json=(value:unknown,status=200)=>Response.json(value,{status,headers:{\"cache-control\":\"no-store\"}});\n return {\n async fetch(request:Request):Promise<Response> {\n try {\n const url=new URL(request.url);\n if(url.pathname!==base&&!url.pathname.startsWith(base+\"/\"))return json({error:{kind:\"not_found\"}},404);\n if(request.method!==\"GET\"&&request.method!==\"HEAD\"&&!originCheck(request))return json({error:{kind:\"forbidden_origin\"}},403);\n const user=await options.resolveUser(request);if(!user)return json({error:{kind:\"unauthorized\"}},401);\n const parts=url.pathname.slice(base.length).split(\"/\").filter(Boolean).map(decodeURIComponent);\n if(parts.length===0&&request.method===\"GET\")return json({version:1,conversations:await service.list(user.id)});\n const id=parts[0];if(!id)return json({error:{kind:\"not_found\"}},404);\n // Always authorize before parsing request bodies or creating a stream.\n await service.snapshot(user.id,id);\n if(parts.length===1&&request.method===\"GET\")return json(await service.snapshot(user.id,id));\n if(parts[1]===\"events\"&&parts.length===2&&request.method===\"GET\"){\n const raw=url.searchParams.get(\"after\")??request.headers.get(\"last-event-id\")??\"0\";\n if(!/^\\d+$/.test(raw))throw Error(\"invalid_cursor\");\n const after=Number(raw);\n const page=await service.events(user.id,id,after);\n if(page.reset)return json({version:1,reset:true},409);\n if(!request.headers.get(\"accept\")?.includes(\"text/event-stream\"))return json(page);\n let dispose=()=>{};\n let cursor=after,closed=false,timer:ReturnType<typeof setTimeout>|undefined;\n const encoder=new TextEncoder();let lastPing=Date.now();\n const stream=new ReadableStream<Uint8Array>({\n start(controller){\n const finish=()=>{if(closed)return;closed=true;clearTimeout(timer);request.signal.removeEventListener(\"abort\",abort);controller.close();};\n const abort=()=>finish();\n request.signal.addEventListener(\"abort\",abort,{once:true});\n dispose=()=>request.signal.removeEventListener(\"abort\",abort);\n const poll=async()=>{\n if(closed)return;\n try {\n if((controller.desiredSize??1)<=0){timer=setTimeout(()=>void poll(),options.pollMs??200);return;}\n const next=await service.events(user.id,id,cursor);\n if(closed)return;\n if(next.reset){controller.enqueue(encoder.encode('event: reset\\ndata: {\"version\":1,\"reset\":true}\\n\\n'));finish();return;}\n for(const event of next.events){if((controller.desiredSize??1)<=0)break;controller.enqueue(encoder.encode(`id: ${event.seq}\\nevent: change\\ndata: ${JSON.stringify(event)}\\n\\n`));cursor=event.seq;}\n if(Date.now()-lastPing>=(options.heartbeatMs??10_000)){controller.enqueue(encoder.encode(\": heartbeat\\n\\n\"));lastPing=Date.now();}\n timer=setTimeout(()=>void poll(),options.pollMs??200);\n }catch(error){options.logError?.(error);finish();}\n };\n if(request.signal.aborted)finish();else void poll();\n },\n cancel(){closed=true;clearTimeout(timer);dispose();},\n });\n return new Response(stream,{headers:{\"content-type\":\"text/event-stream\",\"cache-control\":\"no-cache, no-transform\",\"x-accel-buffering\":\"no\"}});\n }\n if(request.method===\"POST\"){\n const bytes=await readBody(request);const body=JSON.parse(bytes) as Record<string,unknown>;\n if(!body||typeof body!==\"object\"||Array.isArray(body))throw Error(\"invalid_body\");\n if(parts[1]===\"messages\"&&parts.length===2){\n if(typeof body.clientMessageId!==\"string\"||typeof body.text!==\"string\")throw Error(\"invalid_message\");\n const receipt=await service.send(user.id,id,{clientMessageId:body.clientMessageId,text:body.text});return json({version:1,...receipt},202);\n }\n if(parts[1]===\"stop\"&&parts.length===2){if(typeof body.commandId!==\"string\")throw Error(\"invalid_command\");return json({version:1,...await service.stop(user.id,id,body.commandId)},202);}\n if(parts[1]===\"permissions\"&&parts[2]&&parts.length===3){\n if(typeof body.commandId!==\"string\"||!body.decision||typeof body.decision!==\"object\"||Array.isArray(body.decision))throw Error(\"invalid_permission_answer\");\n const decision=body.decision as PermissionDecision;\n if(decision.behavior!==undefined&&decision.behavior!==\"allow\"&&decision.behavior!==\"deny\")throw Error(\"invalid_permission_answer\");\n if(decision.actionId!==undefined&&typeof decision.actionId!==\"string\")throw Error(\"invalid_permission_answer\");\n if(decision.answers!==undefined&&(!decision.answers||typeof decision.answers!==\"object\"||Array.isArray(decision.answers)))throw Error(\"invalid_permission_answer\");\n return json({version:1,...await service.answerPermission(user.id,id,parts[2],body.commandId,decision)},202);\n }\n }\n return json({error:{kind:\"not_found\"}},404);\n }catch(error){\n const kind=error instanceof Error?error.message:\"internal\";\n if(kind===\"not_found\")return json({error:{kind}},404);\n if(kind===\"body_too_large\")return json({error:{kind}},413);\n if([\"busy\",\"message_id_conflict\",\"command_id_conflict\",\"permission_not_pending\",\"executor_offline\",\"executor_not_supported\"].includes(kind))return json({error:{kind,...(kind===\"busy\"&&{code:\"external_conversation_busy\"})}},409);\n if(kind.startsWith(\"invalid_\")||error instanceof SyntaxError||error instanceof URIError)return json({error:{kind:\"invalid\"}},400);\n options.logError?.(error);return json({error:{kind:\"internal\"}},500);\n }\n },\n };\n}\nasync function readBody(request:Request):Promise<string> {\n if(!request.body)return \"{}\";\n const reader=request.body.getReader();let size=0;const chunks:Uint8Array[]=[];\n try{while(true){const {done,value}=await reader.read();if(done)break;size+=value.length;if(size>128_000){await reader.cancel();throw Error(\"body_too_large\");}chunks.push(value);}}finally{reader.releaseLock();}\n const result=new Uint8Array(size);let offset=0;for(const c of chunks){result.set(c,offset);offset+=c.length;}return new TextDecoder().decode(result);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/external.ts"],"sourcesContent":["import type { ExternalConversationService, PermissionDecision } from \"@ykurilov/agent-chat-core/external\";\nimport { createRequestOriginCheck } from \"@ykurilov/agent-chat-core\";\n\nexport interface ExternalChatHandlerOptions {\n service:ExternalConversationService;\n attachments?:{\n settings():Response;\n handle(input:{request:Request;userId:string;chatId:string;segments:string[]}):Promise<Response>;\n };\n basePath?:string;\n resolveUser:(request:Request)=>{id:string}|null|Promise<{id:string}|null>;\n allowedOrigins?:string[];\n pollMs?:number; heartbeatMs?:number;\n logError?:(error:unknown)=>void;\n}\n/** Version 1 native protocol. Standard fetch handler: mount with Hono's app.all(..., c => handler.fetch(c.req.raw)). */\nexport function createExternalChatHandler(options:ExternalChatHandlerOptions) {\n const base=(options.basePath??\"/api/conversations\").replace(/\\/$/,\"\");\n const originCheck=createRequestOriginCheck({allowedOrigins:options.allowedOrigins});\n const service=options.service;\n const json=(value:unknown,status=200)=>Response.json(value,{status,headers:{\"cache-control\":\"no-store\"}});\n return {\n async fetch(request:Request):Promise<Response> {\n try {\n const url=new URL(request.url);\n if(url.pathname!==base&&!url.pathname.startsWith(base+\"/\"))return json({error:{kind:\"not_found\"}},404);\n if(request.method!==\"GET\"&&request.method!==\"HEAD\"&&!originCheck(request))return json({error:{kind:\"forbidden_origin\"}},403);\n const user=await options.resolveUser(request);if(!user)return json({error:{kind:\"unauthorized\"}},401);\n const parts=url.pathname.slice(base.length).split(\"/\").filter(Boolean).map(decodeURIComponent);\n if(parts.length===0&&request.method===\"GET\")return json({version:1,conversations:await service.list(user.id)});\n if(parts.length===2&&parts[0]===\"attachments\"&&parts[1]===\"settings\"&&request.method===\"GET\")return options.attachments?.settings()??json({error:{kind:\"not_found\"}},404);\n const id=parts[0];if(!id)return json({error:{kind:\"not_found\"}},404);\n // Always authorize before parsing request bodies or creating a stream.\n await service.snapshot(user.id,id);\n if(parts[1]===\"attachments\")return options.attachments?.handle({request,userId:user.id,chatId:id,segments:parts.slice(2)})??json({error:{kind:\"not_found\"}},404);\n if(parts[1]===\"model\"&&parts.length===2){\n if(request.method===\"GET\"){await service.start(user.id,id);return json({version:1,modelState:(await service.snapshot(user.id,id)).modelState??null});}\n if(request.method===\"PUT\"){const body=JSON.parse(await readBody(request));return json({version:1,modelState:await service.setModel(user.id,id,body)});}\n }\n if(parts.length===1&&request.method===\"GET\")return json(await service.snapshot(user.id,id));\n if(parts[1]===\"events\"&&parts.length===2&&request.method===\"GET\"){\n const raw=url.searchParams.get(\"after\")??request.headers.get(\"last-event-id\")??\"0\";\n if(!/^\\d+$/.test(raw))throw Error(\"invalid_cursor\");\n const after=Number(raw);\n const page=await service.events(user.id,id,after);\n if(page.reset)return json({version:1,reset:true},409);\n if(!request.headers.get(\"accept\")?.includes(\"text/event-stream\"))return json(page);\n let dispose=()=>{};\n let cursor=after,closed=false,timer:ReturnType<typeof setTimeout>|undefined;\n const encoder=new TextEncoder();let lastPing=Date.now();\n const stream=new ReadableStream<Uint8Array>({\n start(controller){\n const finish=()=>{if(closed)return;closed=true;clearTimeout(timer);request.signal.removeEventListener(\"abort\",abort);controller.close();};\n const abort=()=>finish();\n request.signal.addEventListener(\"abort\",abort,{once:true});\n dispose=()=>request.signal.removeEventListener(\"abort\",abort);\n const poll=async()=>{\n if(closed)return;\n try {\n if((controller.desiredSize??1)<=0){timer=setTimeout(()=>void poll(),options.pollMs??200);return;}\n const next=await service.events(user.id,id,cursor);\n if(closed)return;\n if(next.reset){controller.enqueue(encoder.encode('event: reset\\ndata: {\"version\":1,\"reset\":true}\\n\\n'));finish();return;}\n for(const event of next.events){if((controller.desiredSize??1)<=0)break;controller.enqueue(encoder.encode(`id: ${event.seq}\\nevent: change\\ndata: ${JSON.stringify(event)}\\n\\n`));cursor=event.seq;}\n if(Date.now()-lastPing>=(options.heartbeatMs??10_000)){controller.enqueue(encoder.encode(\": heartbeat\\n\\n\"));lastPing=Date.now();}\n timer=setTimeout(()=>void poll(),options.pollMs??200);\n }catch(error){options.logError?.(error);finish();}\n };\n if(request.signal.aborted)finish();else void poll();\n },\n cancel(){closed=true;clearTimeout(timer);dispose();},\n });\n return new Response(stream,{headers:{\"content-type\":\"text/event-stream\",\"cache-control\":\"no-cache, no-transform\",\"x-accel-buffering\":\"no\"}});\n }\n if(request.method===\"POST\"){\n const bytes=await readBody(request);const body=JSON.parse(bytes) as Record<string,unknown>;\n if(!body||typeof body!==\"object\"||Array.isArray(body))throw Error(\"invalid_body\");\n if(parts[1]===\"messages\"&&parts.length===2){\n if(typeof body.clientMessageId!==\"string\"||typeof body.text!==\"string\")throw Error(\"invalid_message\");\n const receipt=await service.send(user.id,id,{clientMessageId:body.clientMessageId,text:body.text,...(body.attachmentIds!==undefined&&{attachmentIds:body.attachmentIds as string[]})});return json({version:1,...receipt},202);\n }\n if(parts[1]===\"stop\"&&parts.length===2){if(typeof body.commandId!==\"string\")throw Error(\"invalid_command\");return json({version:1,...await service.stop(user.id,id,body.commandId)},202);}\n if(parts[1]===\"permissions\"&&parts[2]&&parts.length===3){\n if(typeof body.commandId!==\"string\"||!body.decision||typeof body.decision!==\"object\"||Array.isArray(body.decision))throw Error(\"invalid_permission_answer\");\n const decision=body.decision as PermissionDecision;\n if(decision.behavior!==undefined&&decision.behavior!==\"allow\"&&decision.behavior!==\"deny\")throw Error(\"invalid_permission_answer\");\n if(decision.actionId!==undefined&&typeof decision.actionId!==\"string\")throw Error(\"invalid_permission_answer\");\n if(decision.answers!==undefined&&(!decision.answers||typeof decision.answers!==\"object\"||Array.isArray(decision.answers)))throw Error(\"invalid_permission_answer\");\n return json({version:1,...await service.answerPermission(user.id,id,parts[2],body.commandId,decision)},202);\n }\n }\n return json({error:{kind:\"not_found\"}},404);\n }catch(error){\n const kind=error instanceof Error?error.message:\"internal\";\n if(kind===\"not_found\")return json({error:{kind}},404);\n if(kind===\"body_too_large\")return json({error:{kind}},413);\n if([\"busy\",\"message_id_conflict\",\"command_id_conflict\",\"permission_not_pending\",\"executor_offline\",\"executor_not_supported\"].includes(kind))return json({error:{kind,...(kind===\"busy\"&&{code:\"external_conversation_busy\"})}},409);\n const code=error&&typeof error===\"object\"&&\"code\" in error&&typeof error.code===\"string\"?error.code:kind;\n if(code.startsWith(\"external_model_\")||kind===\"external_attachments_unsupported\")return json({error:{kind:code,code}},409);\n if(kind.startsWith(\"attachment_\"))return json({error:{kind}},kind===\"attachment_not_found\"?404:422);\n if(kind.startsWith(\"invalid_\")||error instanceof SyntaxError||error instanceof URIError)return json({error:{kind:\"invalid\"}},400);\n options.logError?.(error);return json({error:{kind:\"internal\"}},500);\n }\n },\n };\n}\nasync function readBody(request:Request):Promise<string> {\n if(!request.body)return \"{}\";\n const reader=request.body.getReader();let size=0;const chunks:Uint8Array[]=[];\n try{while(true){const {done,value}=await reader.read();if(done)break;size+=value.length;if(size>128_000){await reader.cancel();throw Error(\"body_too_large\");}chunks.push(value);}}finally{reader.releaseLock();}\n const result=new Uint8Array(size);let offset=0;for(const c of chunks){result.set(c,offset);offset+=c.length;}return new TextDecoder().decode(result);\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,15 @@
1
1
  import type { ExternalConversationService } from "@ykurilov/agent-chat-core/external";
2
2
  export interface ExternalChatHandlerOptions {
3
3
  service: ExternalConversationService;
4
+ attachments?: {
5
+ settings(): Response;
6
+ handle(input: {
7
+ request: Request;
8
+ userId: string;
9
+ chatId: string;
10
+ segments: string[];
11
+ }): Promise<Response>;
12
+ };
4
13
  basePath?: string;
5
14
  resolveUser: (request: Request) => {
6
15
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ykurilov/agent-chat-server",
3
- "version": "0.86.0",
3
+ "version": "0.87.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -50,7 +50,7 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "ai": "7.0.122",
53
- "@ykurilov/agent-chat-core": "0.86.0"
53
+ "@ykurilov/agent-chat-core": "0.87.0"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "hono": "^4.13.10"