@harmonyos-arkts/opencode-acp 0.0.8 → 0.0.10

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/index.cjs CHANGED
@@ -20422,9 +20422,15 @@ function sanitize(obj, depth = 0) {
20422
20422
  } else if (val && typeof val === "object" && !Array.isArray(val)) {
20423
20423
  result[key] = sanitize(val, depth + 1);
20424
20424
  } else if (val && Array.isArray(val)) {
20425
- result[key] = val.map(
20426
- (item) => typeof item === "string" && item.length > 2e3 ? item.slice(0, 2e3) + `... [${item.length} chars total]` : item && typeof item === "object" ? sanitize(item, depth + 1) : item
20427
- );
20425
+ result[key] = val.map((item) => {
20426
+ if (typeof item === "string" && item.length > 2e3) {
20427
+ return item.slice(0, 2e3) + `... [${item.length} chars total]`;
20428
+ }
20429
+ if (item && typeof item === "object") {
20430
+ return sanitize(item, depth + 1);
20431
+ }
20432
+ return item;
20433
+ });
20428
20434
  } else {
20429
20435
  result[key] = val;
20430
20436
  }
@@ -20462,12 +20468,12 @@ function toLocations(toolName, input) {
20462
20468
  case "read":
20463
20469
  case "edit":
20464
20470
  case "write":
20465
- return input["filePath"] ? [{ path: input["filePath"] }] : [];
20471
+ return input.filePath ? [{ path: input.filePath }] : [];
20466
20472
  case "glob":
20467
20473
  case "grep":
20468
- return input["path"] ? [{ path: input["path"] }] : [];
20474
+ return input.path ? [{ path: input.path }] : [];
20469
20475
  case "list":
20470
- return input["path"] ? [{ path: input["path"] }] : [];
20476
+ return input.path ? [{ path: input.path }] : [];
20471
20477
  default:
20472
20478
  return [];
20473
20479
  }
@@ -20492,18 +20498,30 @@ function parseUri(uri) {
20492
20498
  return { type: "text", text: uri };
20493
20499
  }
20494
20500
  }
20501
+ function buildUpdateLogPayload(updateType, update) {
20502
+ if (updateType === "agent_message_chunk" || updateType === "agent_thought_chunk") {
20503
+ return { messageId: update.messageId?.slice(0, 12), delta: update.content?.text?.slice(0, 200) };
20504
+ }
20505
+ if (updateType === "tool_call" || updateType === "tool_call_update") {
20506
+ return { toolCallId: update.toolCallId, tool: update.title, kind: update.kind, status: update.status };
20507
+ }
20508
+ if (updateType === "session_info_update") {
20509
+ return { title: update.title, _meta: update._meta };
20510
+ }
20511
+ if (updateType === "usage_update") {
20512
+ return { used: update.used, size: update.size, cost: update.cost, _meta: update._meta, error: update._meta?.error?.message };
20513
+ }
20514
+ if (update._meta?.error) {
20515
+ return { error: update._meta.error?.message ?? update._meta.error };
20516
+ }
20517
+ return {};
20518
+ }
20495
20519
  async function sendToClient(connection, params) {
20496
20520
  const updateType = params.update.sessionUpdate;
20497
20521
  const update = params.update;
20498
20522
  acpOut(`sessionUpdate.${updateType}`, {
20499
20523
  sessionId: params.sessionId.slice(0, 12),
20500
- ...updateType === "agent_message_chunk" || updateType === "agent_thought_chunk" ? { messageId: update.messageId?.slice(0, 12), delta: update.content?.text?.slice(0, 200) } : updateType === "tool_call" || updateType === "tool_call_update" ? { toolCallId: update.toolCallId, tool: update.title, kind: update.kind, status: update.status } : updateType === "session_info_update" ? { title: update.title, _meta: update._meta } : updateType === "usage_update" ? {
20501
- used: update.used,
20502
- size: update.size,
20503
- cost: update.cost,
20504
- _meta: update._meta,
20505
- error: update._meta?.error?.message
20506
- } : update._meta?.error ? { error: update._meta.error?.message ?? update._meta.error } : {}
20524
+ ...buildUpdateLogPayload(updateType, update)
20507
20525
  });
20508
20526
  return connection.sessionUpdate(params);
20509
20527
  }
@@ -20534,9 +20552,19 @@ function normalizeError(err) {
20534
20552
  }
20535
20553
  if (typeof err === "object") {
20536
20554
  const o = err;
20537
- const message = typeof o.message === "string" && o.message.trim() ? o.message.trim() : typeof o.error === "string" && o.error.trim() ? o.error.trim() : void 0;
20555
+ let message;
20556
+ if (typeof o.message === "string" && o.message.trim()) {
20557
+ message = o.message.trim();
20558
+ } else if (typeof o.error === "string" && o.error.trim()) {
20559
+ message = o.error.trim();
20560
+ }
20538
20561
  if (!message) return void 0;
20539
- const cause = o.cause instanceof Error ? o.cause.message : typeof o.cause === "object" && o.cause !== null && typeof o.cause.message === "string" ? o.cause.message : void 0;
20562
+ let cause;
20563
+ if (o.cause instanceof Error) {
20564
+ cause = o.cause.message;
20565
+ } else if (typeof o.cause === "object" && o.cause !== null && typeof o.cause.message === "string") {
20566
+ cause = o.cause.message;
20567
+ }
20540
20568
  return {
20541
20569
  code: inferCode(o),
20542
20570
  message,
@@ -20621,11 +20649,13 @@ var EventHandler = class {
20621
20649
  aiCodeChangeStats = /* @__PURE__ */ new Map();
20622
20650
  lastSubscriptionErrorBroadcastAt = 0;
20623
20651
  turnTimeoutTracker;
20652
+ cancelTurnTracker;
20624
20653
  constructor(deps) {
20625
20654
  this.connection = deps.connection;
20626
20655
  this.sdk = deps.sdk;
20627
20656
  this.sessionManager = deps.sessionManager;
20628
20657
  this.turnTimeoutTracker = deps.turnTimeoutTracker;
20658
+ this.cancelTurnTracker = deps.cancelTurnTracker;
20629
20659
  }
20630
20660
  start() {
20631
20661
  if (this.started) return;
@@ -20689,6 +20719,10 @@ var EventHandler = class {
20689
20719
  if (!session) return void 0;
20690
20720
  return { sessionId: session.id, cwd: session.cwd };
20691
20721
  }
20722
+ /** Drop streaming chunks while a cancelled turn is unwinding on the OpenCode server. */
20723
+ shouldDropStreaming(sessionId) {
20724
+ return this.cancelTurnTracker?.isCancelled(sessionId) ?? false;
20725
+ }
20692
20726
  // ─── Event Handling ──────────────────────────────────────────────
20693
20727
  async handleEvent(event) {
20694
20728
  if (event.type === "server.heartbeat") {
@@ -20718,6 +20752,26 @@ var EventHandler = class {
20718
20752
  }
20719
20753
  return;
20720
20754
  }
20755
+ case "session.status": {
20756
+ ocEvent("session.status", event);
20757
+ const props = event.properties;
20758
+ const sessionID = props.sessionID;
20759
+ if (!sessionID) return;
20760
+ const resolved = this.resolveSession(sessionID);
20761
+ if (!resolved) return;
20762
+ await sendToClient(this.connection, {
20763
+ sessionId: resolved.sessionId,
20764
+ update: {
20765
+ sessionUpdate: "session_info_update",
20766
+ _meta: {
20767
+ opencodeStatus: props.status?.type ?? "unknown",
20768
+ opencodeStatusDetail: props.status
20769
+ }
20770
+ }
20771
+ }).catch(() => {
20772
+ });
20773
+ return;
20774
+ }
20721
20775
  case "permission.asked": {
20722
20776
  ocEvent("permission.asked", event);
20723
20777
  const permission = event.properties;
@@ -20757,8 +20811,8 @@ var EventHandler = class {
20757
20811
  }
20758
20812
  if (res.outcome.optionId !== "reject" && permission.permission === "edit") {
20759
20813
  const metadata = permission.metadata || {};
20760
- const filepath = typeof metadata["filepath"] === "string" ? metadata["filepath"] : "";
20761
- const diff = typeof metadata["diff"] === "string" ? metadata["diff"] : "";
20814
+ const filepath = typeof metadata.filepath === "string" ? metadata.filepath : "";
20815
+ const diff = typeof metadata.diff === "string" ? metadata.diff : "";
20762
20816
  if (filepath && diff) {
20763
20817
  const fileResult = await this.sdk.file.read({ path: filepath, directory }).then((x) => x.data).catch(() => void 0);
20764
20818
  const original = typeof fileResult?.content === "string" ? fileResult.content : "";
@@ -20865,7 +20919,7 @@ var EventHandler = class {
20865
20919
  if (part.type === "tool") {
20866
20920
  await this.handleToolPart(resolved.sessionId, part);
20867
20921
  }
20868
- if (part.type === "text" && typeof part.text === "string" && part.ignored === true) {
20922
+ if (part.type === "text" && typeof part.text === "string" && part.ignored === true && !this.shouldDropStreaming(resolved.sessionId)) {
20869
20923
  await sendToClient(this.connection, {
20870
20924
  sessionId: resolved.sessionId,
20871
20925
  update: {
@@ -20884,6 +20938,7 @@ var EventHandler = class {
20884
20938
  const resolved = this.resolveSession(props.sessionID);
20885
20939
  if (!resolved) return;
20886
20940
  const sessionId = resolved.sessionId;
20941
+ if (this.shouldDropStreaming(sessionId)) return;
20887
20942
  const partMeta = this.partMetaIndex.get(props.partID);
20888
20943
  if (!partMeta) return;
20889
20944
  if (partMeta.type === "text" && props.field === "text" && partMeta.ignored !== true) {
@@ -20927,9 +20982,8 @@ var EventHandler = class {
20927
20982
  this.fileDiffStats.set(resolved.sessionId, { additions, deletions, files: diff.length });
20928
20983
  return;
20929
20984
  }
20930
- default: {
20985
+ default:
20931
20986
  return;
20932
- }
20933
20987
  }
20934
20988
  }
20935
20989
  // ─── Child Session Announcement ──────────────────────────────────
@@ -20958,7 +21012,9 @@ var EventHandler = class {
20958
21012
  }
20959
21013
  // ─── Tool Part Handling ──────────────────────────────────────────
20960
21014
  async handleToolPart(sessionId, part) {
21015
+ const dropStreaming = this.shouldDropStreaming(sessionId);
20961
21016
  if (!this.toolStarts.has(part.callID)) {
21017
+ if (dropStreaming) return;
20962
21018
  this.toolStarts.add(part.callID);
20963
21019
  this.turnTimeoutTracker?.onToolCallStart(sessionId);
20964
21020
  const session = this.sessionManager.tryGet(sessionId);
@@ -20993,6 +21049,7 @@ var EventHandler = class {
20993
21049
  this.bashSnapshots.delete(part.callID);
20994
21050
  return;
20995
21051
  case "running": {
21052
+ if (dropStreaming) return;
20996
21053
  const output = this.bashOutput(part);
20997
21054
  const content = [];
20998
21055
  if (output) {
@@ -21028,7 +21085,7 @@ var EventHandler = class {
21028
21085
  }
21029
21086
  this.bashSnapshots.delete(part.callID);
21030
21087
  if (part.tool === "task" && part.state.metadata) {
21031
- const childSessionId = typeof part.state.metadata["sessionId"] === "string" ? part.state.metadata["sessionId"] : void 0;
21088
+ const childSessionId = typeof part.state.metadata.sessionId === "string" ? part.state.metadata.sessionId : void 0;
21032
21089
  if (childSessionId && this.sessionManager.tryGet(childSessionId)) {
21033
21090
  const tracker = this.childToolCounts.get(childSessionId);
21034
21091
  await this.sendChildSessionFinished(
@@ -21058,9 +21115,14 @@ var EventHandler = class {
21058
21115
  ];
21059
21116
  if (kind === "edit") {
21060
21117
  const input = part.state.input;
21061
- const filePath = typeof input["filePath"] === "string" ? input["filePath"] : "";
21062
- const oldText = typeof input["oldString"] === "string" ? input["oldString"] : "";
21063
- const newText = typeof input["newString"] === "string" ? input["newString"] : typeof input["content"] === "string" ? input["content"] : "";
21118
+ const filePath = typeof input.filePath === "string" ? input.filePath : "";
21119
+ const oldText = typeof input.oldString === "string" ? input.oldString : "";
21120
+ let newText = "";
21121
+ if (typeof input.newString === "string") {
21122
+ newText = input.newString;
21123
+ } else if (typeof input.content === "string") {
21124
+ newText = input.content;
21125
+ }
21064
21126
  content.push({ type: "diff", path: filePath, oldText, newText });
21065
21127
  this.accumulateAICodeChangeStats(sessionId, part);
21066
21128
  }
@@ -21109,7 +21171,7 @@ var EventHandler = class {
21109
21171
  }
21110
21172
  this.bashSnapshots.delete(part.callID);
21111
21173
  if (part.tool === "task" && part.state.metadata) {
21112
- const childSessionId = typeof part.state.metadata["sessionId"] === "string" ? part.state.metadata["sessionId"] : void 0;
21174
+ const childSessionId = typeof part.state.metadata.sessionId === "string" ? part.state.metadata.sessionId : void 0;
21113
21175
  if (childSessionId && this.sessionManager.tryGet(childSessionId)) {
21114
21176
  const tracker = this.childToolCounts.get(childSessionId);
21115
21177
  const errText = typeof part.state.error === "string" && part.state.error.trim() ? part.state.error.trim() : "Sub-agent task failed";
@@ -21154,6 +21216,8 @@ var EventHandler = class {
21154
21216
  });
21155
21217
  return;
21156
21218
  }
21219
+ default:
21220
+ return;
21157
21221
  }
21158
21222
  }
21159
21223
  /**
@@ -21186,7 +21250,7 @@ var EventHandler = class {
21186
21250
  bashOutput(part) {
21187
21251
  if (part.tool !== "bash") return;
21188
21252
  if (!("metadata" in part.state) || !part.state.metadata || typeof part.state.metadata !== "object") return;
21189
- const output = part.state.metadata["output"];
21253
+ const output = part.state.metadata.output;
21190
21254
  if (typeof output !== "string") return;
21191
21255
  return output;
21192
21256
  }
@@ -21205,7 +21269,7 @@ var EventHandler = class {
21205
21269
  accumulateAICodeChangeStats(sessionId, part) {
21206
21270
  if (part.state.status !== "completed") return;
21207
21271
  const meta3 = part.state.metadata ?? {};
21208
- const aiCodeChange = meta3["aiCodeChange"];
21272
+ const aiCodeChange = meta3.aiCodeChange;
21209
21273
  if (!aiCodeChange || aiCodeChange.additions === 0 && aiCodeChange.deletions === 0) return;
21210
21274
  const fileMap = this.aiCodeChangeStats.get(sessionId) ?? /* @__PURE__ */ new Map();
21211
21275
  const existing = fileMap.get(aiCodeChange.file) ?? { additions: 0, deletions: 0 };
@@ -21346,14 +21410,19 @@ function sessionIdFromEvent(event) {
21346
21410
  return typeof props.sessionID === "string" ? props.sessionID : void 0;
21347
21411
  case "session.diff":
21348
21412
  case "session.updated":
21349
- case "session.created":
21350
- return typeof props.sessionID === "string" ? props.sessionID : typeof props.info?.id === "string" ? props.info.id : void 0;
21413
+ case "session.created": {
21414
+ if (typeof props.sessionID === "string") return props.sessionID;
21415
+ const info = props.info;
21416
+ if (typeof info?.id === "string") return info.id;
21417
+ return void 0;
21418
+ }
21351
21419
  case "permission.asked": {
21352
21420
  const permission = props;
21353
21421
  return typeof permission.sessionID === "string" ? permission.sessionID : void 0;
21354
21422
  }
21423
+ default:
21424
+ return void 0;
21355
21425
  }
21356
- return void 0;
21357
21426
  }
21358
21427
  var TurnTimeoutTracker = class {
21359
21428
  constructor(sessionManager) {
@@ -21453,6 +21522,28 @@ var TurnTimeoutTracker = class {
21453
21522
  }
21454
21523
  };
21455
21524
 
21525
+ // src/cancel-turn-tracker.ts
21526
+ var CancelTurnTracker = class {
21527
+ cancelled = /* @__PURE__ */ new Set();
21528
+ /** New prompt turn — allow streaming again for this session subtree. */
21529
+ beginTurn(rootSessionId, relatedSessionIds = []) {
21530
+ this.cancelled.delete(rootSessionId);
21531
+ for (const id of relatedSessionIds) {
21532
+ this.cancelled.delete(id);
21533
+ }
21534
+ }
21535
+ /** User cancelled — drop streaming for root and related (child) sessions. */
21536
+ markCancelled(rootSessionId, relatedSessionIds = []) {
21537
+ this.cancelled.add(rootSessionId);
21538
+ for (const id of relatedSessionIds) {
21539
+ this.cancelled.add(id);
21540
+ }
21541
+ }
21542
+ isCancelled(sessionId) {
21543
+ return this.cancelled.has(sessionId);
21544
+ }
21545
+ };
21546
+
21456
21547
  // src/agent.ts
21457
21548
  function isApiKeyError(err) {
21458
21549
  return err instanceof Error && err.name === "AI_LoadAPIKeyError";
@@ -21466,6 +21557,7 @@ var Agent = class {
21466
21557
  authProvider;
21467
21558
  mcpManager;
21468
21559
  turnTimeoutTracker;
21560
+ cancelTurnTracker = new CancelTurnTracker();
21469
21561
  constructor(config2) {
21470
21562
  this.config = config2;
21471
21563
  this.sdk = config2.sdk;
@@ -21480,7 +21572,8 @@ var Agent = class {
21480
21572
  connection,
21481
21573
  sdk: this.sdk,
21482
21574
  sessionManager: this.sessionManager,
21483
- turnTimeoutTracker: this.turnTimeoutTracker
21575
+ turnTimeoutTracker: this.turnTimeoutTracker,
21576
+ cancelTurnTracker: this.cancelTurnTracker
21484
21577
  });
21485
21578
  this.eventHandler.start();
21486
21579
  }
@@ -21825,6 +21918,7 @@ var Agent = class {
21825
21918
  const cmd = this.parseCommand(parts);
21826
21919
  const modelLabel = `${model.providerID}/${model.modelID}`;
21827
21920
  ocCall("session.prompt", { sessionID, agent, model: modelLabel });
21921
+ this.cancelTurnTracker.beginTurn(sessionID, this.sessionManager.getRelatedSessions(sessionID));
21828
21922
  this.turnTimeoutTracker.onPromptStart(sessionID, { model: modelLabel });
21829
21923
  try {
21830
21924
  if (!cmd) {
@@ -21936,6 +22030,19 @@ var Agent = class {
21936
22030
  async cancel(params) {
21937
22031
  acpIn("cancel", { sessionId: params.sessionId });
21938
22032
  const session = this.sessionManager.get(params.sessionId);
22033
+ const related = this.sessionManager.getRelatedSessions(params.sessionId);
22034
+ this.cancelTurnTracker.markCancelled(params.sessionId, related);
22035
+ await sendToClient(this.connection, {
22036
+ sessionId: params.sessionId,
22037
+ update: {
22038
+ sessionUpdate: "session_info_update",
22039
+ _meta: {
22040
+ turnCancelled: true,
22041
+ opencodeStatus: "idle"
22042
+ }
22043
+ }
22044
+ }).catch(() => {
22045
+ });
21939
22046
  ocCall("session.abort", { sessionID: params.sessionId });
21940
22047
  await this.sdk.session.abort({
21941
22048
  sessionID: params.sessionId,
@@ -21973,7 +22080,7 @@ var Agent = class {
21973
22080
  const session = this.sessionManager.get(params.sessionId);
21974
22081
  const providers = await this.sdk.config.providers({ directory: session.cwd }).then((x) => x.data?.providers ?? []).catch(() => []);
21975
22082
  const sortedProviders = [...providers].sort(
21976
- (a, b) => a.name.toLowerCase() < b.name.toLowerCase() ? -1 : a.name.toLowerCase() > b.name.toLowerCase() ? 1 : 0
22083
+ (a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())
21977
22084
  );
21978
22085
  if (params.configId === "model") {
21979
22086
  if (typeof params.value !== "string") throw RequestError.invalidParams("model value must be a string");
@@ -22102,7 +22209,7 @@ var Agent = class {
22102
22209
  const model = await this.defaultModel(params.cwd);
22103
22210
  const providers = await this.sdk.config.providers({ directory: params.cwd }).then((x) => x.data?.providers ?? []).catch(() => []);
22104
22211
  const sortedProviders = [...providers].sort(
22105
- (a, b) => a.name.toLowerCase() < b.name.toLowerCase() ? -1 : a.name.toLowerCase() > b.name.toLowerCase() ? 1 : 0
22212
+ (a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())
22106
22213
  );
22107
22214
  const availableModels = sortedProviders.flatMap(
22108
22215
  (provider) => Object.values(provider.models).flatMap((modelInfo) => {
@@ -22224,7 +22331,12 @@ var Agent = class {
22224
22331
  if (part.type === "tool") {
22225
22332
  await this.replayToolPart(sessionId, part);
22226
22333
  } else if (part.type === "text" && part.text) {
22227
- const audience = part.synthetic ? ["assistant"] : part.ignored ? ["user"] : void 0;
22334
+ let audience;
22335
+ if (part.synthetic) {
22336
+ audience = ["assistant"];
22337
+ } else if (part.ignored) {
22338
+ audience = ["user"];
22339
+ }
22228
22340
  await sendToClient(this.connection, {
22229
22341
  sessionId,
22230
22342
  update: {
@@ -22324,11 +22436,17 @@ var Agent = class {
22324
22436
  const content = [{ type: "content", content: { type: "text", text: part.state.output } }];
22325
22437
  if (kind === "edit") {
22326
22438
  const input = part.state.input;
22439
+ let newText = "";
22440
+ if (typeof input.newString === "string") {
22441
+ newText = input.newString;
22442
+ } else if (typeof input.content === "string") {
22443
+ newText = input.content;
22444
+ }
22327
22445
  content.push({
22328
22446
  type: "diff",
22329
- path: typeof input["filePath"] === "string" ? input["filePath"] : "",
22330
- oldText: typeof input["oldString"] === "string" ? input["oldString"] : "",
22331
- newText: typeof input["newString"] === "string" ? input["newString"] : typeof input["content"] === "string" ? input["content"] : ""
22447
+ path: typeof input.filePath === "string" ? input.filePath : "",
22448
+ oldText: typeof input.oldString === "string" ? input.oldString : "",
22449
+ newText
22332
22450
  });
22333
22451
  this.eventHandler.accumulateAICodeChangeStats(sessionId, part);
22334
22452
  }
@@ -22384,6 +22502,8 @@ var Agent = class {
22384
22502
  }).catch(() => {
22385
22503
  });
22386
22504
  break;
22505
+ default:
22506
+ break;
22387
22507
  }
22388
22508
  }
22389
22509
  async sendUsageUpdate(sessionId, directory, turnError) {
@@ -22473,11 +22593,18 @@ var Agent = class {
22473
22593
  } : { ...cs };
22474
22594
  }
22475
22595
  }
22476
- const totalAICodeChangeStats = parentStats ? childAICodeChangeStats ? {
22477
- additions: parentStats.additions + childAICodeChangeStats.additions,
22478
- deletions: parentStats.deletions + childAICodeChangeStats.deletions,
22479
- files: parentStats.files + childAICodeChangeStats.files
22480
- } : parentStats : childAICodeChangeStats;
22596
+ let totalAICodeChangeStats;
22597
+ if (parentStats && childAICodeChangeStats) {
22598
+ totalAICodeChangeStats = {
22599
+ additions: parentStats.additions + childAICodeChangeStats.additions,
22600
+ deletions: parentStats.deletions + childAICodeChangeStats.deletions,
22601
+ files: parentStats.files + childAICodeChangeStats.files
22602
+ };
22603
+ } else if (parentStats) {
22604
+ totalAICodeChangeStats = parentStats;
22605
+ } else {
22606
+ totalAICodeChangeStats = childAICodeChangeStats;
22607
+ }
22481
22608
  if (totalAICodeChangeStats) {
22482
22609
  _meta.aiCodeChange = totalAICodeChangeStats;
22483
22610
  }
@@ -22533,6 +22660,8 @@ var Agent = class {
22533
22660
  }
22534
22661
  break;
22535
22662
  }
22663
+ default:
22664
+ break;
22536
22665
  }
22537
22666
  }
22538
22667
  return result;