@link-assistant/agent 0.0.8

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.
Files changed (133) hide show
  1. package/EXAMPLES.md +383 -0
  2. package/LICENSE +24 -0
  3. package/MODELS.md +95 -0
  4. package/README.md +388 -0
  5. package/TOOLS.md +134 -0
  6. package/package.json +89 -0
  7. package/src/agent/agent.ts +150 -0
  8. package/src/agent/generate.txt +75 -0
  9. package/src/auth/index.ts +64 -0
  10. package/src/bun/index.ts +96 -0
  11. package/src/bus/global.ts +10 -0
  12. package/src/bus/index.ts +119 -0
  13. package/src/cli/bootstrap.js +41 -0
  14. package/src/cli/bootstrap.ts +17 -0
  15. package/src/cli/cmd/agent.ts +165 -0
  16. package/src/cli/cmd/cmd.ts +5 -0
  17. package/src/cli/cmd/export.ts +88 -0
  18. package/src/cli/cmd/mcp.ts +80 -0
  19. package/src/cli/cmd/models.ts +58 -0
  20. package/src/cli/cmd/run.ts +359 -0
  21. package/src/cli/cmd/stats.ts +276 -0
  22. package/src/cli/error.ts +27 -0
  23. package/src/command/index.ts +73 -0
  24. package/src/command/template/initialize.txt +10 -0
  25. package/src/config/config.ts +705 -0
  26. package/src/config/markdown.ts +41 -0
  27. package/src/file/ripgrep.ts +391 -0
  28. package/src/file/time.ts +38 -0
  29. package/src/file/watcher.ts +75 -0
  30. package/src/file.ts +6 -0
  31. package/src/flag/flag.ts +19 -0
  32. package/src/format/formatter.ts +248 -0
  33. package/src/format/index.ts +137 -0
  34. package/src/global/index.ts +52 -0
  35. package/src/id/id.ts +72 -0
  36. package/src/index.js +371 -0
  37. package/src/mcp/index.ts +289 -0
  38. package/src/patch/index.ts +622 -0
  39. package/src/project/bootstrap.ts +22 -0
  40. package/src/project/instance.ts +67 -0
  41. package/src/project/project.ts +105 -0
  42. package/src/project/state.ts +65 -0
  43. package/src/provider/models-macro.ts +11 -0
  44. package/src/provider/models.ts +98 -0
  45. package/src/provider/opencode.js +47 -0
  46. package/src/provider/provider.ts +636 -0
  47. package/src/provider/transform.ts +241 -0
  48. package/src/server/project.ts +48 -0
  49. package/src/server/server.ts +249 -0
  50. package/src/session/agent.js +204 -0
  51. package/src/session/compaction.ts +249 -0
  52. package/src/session/index.ts +380 -0
  53. package/src/session/message-v2.ts +758 -0
  54. package/src/session/message.ts +189 -0
  55. package/src/session/processor.ts +356 -0
  56. package/src/session/prompt/anthropic-20250930.txt +166 -0
  57. package/src/session/prompt/anthropic.txt +105 -0
  58. package/src/session/prompt/anthropic_spoof.txt +1 -0
  59. package/src/session/prompt/beast.txt +147 -0
  60. package/src/session/prompt/build-switch.txt +5 -0
  61. package/src/session/prompt/codex.txt +318 -0
  62. package/src/session/prompt/copilot-gpt-5.txt +143 -0
  63. package/src/session/prompt/gemini.txt +155 -0
  64. package/src/session/prompt/grok-code.txt +1 -0
  65. package/src/session/prompt/plan.txt +8 -0
  66. package/src/session/prompt/polaris.txt +107 -0
  67. package/src/session/prompt/qwen.txt +109 -0
  68. package/src/session/prompt/summarize-turn.txt +5 -0
  69. package/src/session/prompt/summarize.txt +10 -0
  70. package/src/session/prompt/title.txt +25 -0
  71. package/src/session/prompt.ts +1390 -0
  72. package/src/session/retry.ts +53 -0
  73. package/src/session/revert.ts +108 -0
  74. package/src/session/status.ts +75 -0
  75. package/src/session/summary.ts +179 -0
  76. package/src/session/system.ts +138 -0
  77. package/src/session/todo.ts +36 -0
  78. package/src/snapshot/index.ts +197 -0
  79. package/src/storage/storage.ts +226 -0
  80. package/src/tool/bash.ts +193 -0
  81. package/src/tool/bash.txt +121 -0
  82. package/src/tool/batch.ts +173 -0
  83. package/src/tool/batch.txt +28 -0
  84. package/src/tool/codesearch.ts +123 -0
  85. package/src/tool/codesearch.txt +12 -0
  86. package/src/tool/edit.ts +604 -0
  87. package/src/tool/edit.txt +10 -0
  88. package/src/tool/glob.ts +65 -0
  89. package/src/tool/glob.txt +6 -0
  90. package/src/tool/grep.ts +116 -0
  91. package/src/tool/grep.txt +8 -0
  92. package/src/tool/invalid.ts +17 -0
  93. package/src/tool/ls.ts +110 -0
  94. package/src/tool/ls.txt +1 -0
  95. package/src/tool/multiedit.ts +46 -0
  96. package/src/tool/multiedit.txt +41 -0
  97. package/src/tool/patch.ts +188 -0
  98. package/src/tool/patch.txt +1 -0
  99. package/src/tool/read.ts +201 -0
  100. package/src/tool/read.txt +12 -0
  101. package/src/tool/registry.ts +87 -0
  102. package/src/tool/task.ts +126 -0
  103. package/src/tool/task.txt +60 -0
  104. package/src/tool/todo.ts +39 -0
  105. package/src/tool/todoread.txt +14 -0
  106. package/src/tool/todowrite.txt +167 -0
  107. package/src/tool/tool.ts +66 -0
  108. package/src/tool/webfetch.ts +171 -0
  109. package/src/tool/webfetch.txt +14 -0
  110. package/src/tool/websearch.ts +133 -0
  111. package/src/tool/websearch.txt +11 -0
  112. package/src/tool/write.ts +33 -0
  113. package/src/tool/write.txt +8 -0
  114. package/src/util/binary.ts +41 -0
  115. package/src/util/context.ts +25 -0
  116. package/src/util/defer.ts +12 -0
  117. package/src/util/error.ts +54 -0
  118. package/src/util/eventloop.ts +20 -0
  119. package/src/util/filesystem.ts +69 -0
  120. package/src/util/fn.ts +11 -0
  121. package/src/util/iife.ts +3 -0
  122. package/src/util/keybind.ts +79 -0
  123. package/src/util/lazy.ts +11 -0
  124. package/src/util/locale.ts +39 -0
  125. package/src/util/lock.ts +98 -0
  126. package/src/util/log.ts +177 -0
  127. package/src/util/queue.ts +19 -0
  128. package/src/util/rpc.ts +42 -0
  129. package/src/util/scrap.ts +10 -0
  130. package/src/util/signal.ts +12 -0
  131. package/src/util/timeout.ts +14 -0
  132. package/src/util/token.ts +7 -0
  133. package/src/util/wildcard.ts +54 -0
@@ -0,0 +1,189 @@
1
+ import z from "zod"
2
+ import { NamedError } from "../util/error"
3
+
4
+ export namespace Message {
5
+ export const OutputLengthError = NamedError.create("MessageOutputLengthError", z.object({}))
6
+ export const AuthError = NamedError.create(
7
+ "ProviderAuthError",
8
+ z.object({
9
+ providerID: z.string(),
10
+ message: z.string(),
11
+ }),
12
+ )
13
+
14
+ export const ToolCall = z
15
+ .object({
16
+ state: z.literal("call"),
17
+ step: z.number().optional(),
18
+ toolCallId: z.string(),
19
+ toolName: z.string(),
20
+ args: z.custom<Required<unknown>>(),
21
+ })
22
+ .meta({
23
+ ref: "ToolCall",
24
+ })
25
+ export type ToolCall = z.infer<typeof ToolCall>
26
+
27
+ export const ToolPartialCall = z
28
+ .object({
29
+ state: z.literal("partial-call"),
30
+ step: z.number().optional(),
31
+ toolCallId: z.string(),
32
+ toolName: z.string(),
33
+ args: z.custom<Required<unknown>>(),
34
+ })
35
+ .meta({
36
+ ref: "ToolPartialCall",
37
+ })
38
+ export type ToolPartialCall = z.infer<typeof ToolPartialCall>
39
+
40
+ export const ToolResult = z
41
+ .object({
42
+ state: z.literal("result"),
43
+ step: z.number().optional(),
44
+ toolCallId: z.string(),
45
+ toolName: z.string(),
46
+ args: z.custom<Required<unknown>>(),
47
+ result: z.string(),
48
+ })
49
+ .meta({
50
+ ref: "ToolResult",
51
+ })
52
+ export type ToolResult = z.infer<typeof ToolResult>
53
+
54
+ export const ToolInvocation = z.discriminatedUnion("state", [ToolCall, ToolPartialCall, ToolResult]).meta({
55
+ ref: "ToolInvocation",
56
+ })
57
+ export type ToolInvocation = z.infer<typeof ToolInvocation>
58
+
59
+ export const TextPart = z
60
+ .object({
61
+ type: z.literal("text"),
62
+ text: z.string(),
63
+ })
64
+ .meta({
65
+ ref: "TextPart",
66
+ })
67
+ export type TextPart = z.infer<typeof TextPart>
68
+
69
+ export const ReasoningPart = z
70
+ .object({
71
+ type: z.literal("reasoning"),
72
+ text: z.string(),
73
+ providerMetadata: z.record(z.string(), z.any()).optional(),
74
+ })
75
+ .meta({
76
+ ref: "ReasoningPart",
77
+ })
78
+ export type ReasoningPart = z.infer<typeof ReasoningPart>
79
+
80
+ export const ToolInvocationPart = z
81
+ .object({
82
+ type: z.literal("tool-invocation"),
83
+ toolInvocation: ToolInvocation,
84
+ })
85
+ .meta({
86
+ ref: "ToolInvocationPart",
87
+ })
88
+ export type ToolInvocationPart = z.infer<typeof ToolInvocationPart>
89
+
90
+ export const SourceUrlPart = z
91
+ .object({
92
+ type: z.literal("source-url"),
93
+ sourceId: z.string(),
94
+ url: z.string(),
95
+ title: z.string().optional(),
96
+ providerMetadata: z.record(z.string(), z.any()).optional(),
97
+ })
98
+ .meta({
99
+ ref: "SourceUrlPart",
100
+ })
101
+ export type SourceUrlPart = z.infer<typeof SourceUrlPart>
102
+
103
+ export const FilePart = z
104
+ .object({
105
+ type: z.literal("file"),
106
+ mediaType: z.string(),
107
+ filename: z.string().optional(),
108
+ url: z.string(),
109
+ })
110
+ .meta({
111
+ ref: "FilePart",
112
+ })
113
+ export type FilePart = z.infer<typeof FilePart>
114
+
115
+ export const StepStartPart = z
116
+ .object({
117
+ type: z.literal("step-start"),
118
+ })
119
+ .meta({
120
+ ref: "StepStartPart",
121
+ })
122
+ export type StepStartPart = z.infer<typeof StepStartPart>
123
+
124
+ export const MessagePart = z
125
+ .discriminatedUnion("type", [TextPart, ReasoningPart, ToolInvocationPart, SourceUrlPart, FilePart, StepStartPart])
126
+ .meta({
127
+ ref: "MessagePart",
128
+ })
129
+ export type MessagePart = z.infer<typeof MessagePart>
130
+
131
+ export const Info = z
132
+ .object({
133
+ id: z.string(),
134
+ role: z.enum(["user", "assistant"]),
135
+ parts: z.array(MessagePart),
136
+ metadata: z
137
+ .object({
138
+ time: z.object({
139
+ created: z.number(),
140
+ completed: z.number().optional(),
141
+ }),
142
+ error: z
143
+ .discriminatedUnion("name", [AuthError.Schema, NamedError.Unknown.Schema, OutputLengthError.Schema])
144
+ .optional(),
145
+ sessionID: z.string(),
146
+ tool: z.record(
147
+ z.string(),
148
+ z
149
+ .object({
150
+ title: z.string(),
151
+ snapshot: z.string().optional(),
152
+ time: z.object({
153
+ start: z.number(),
154
+ end: z.number(),
155
+ }),
156
+ })
157
+ .catchall(z.any()),
158
+ ),
159
+ assistant: z
160
+ .object({
161
+ system: z.string().array(),
162
+ modelID: z.string(),
163
+ providerID: z.string(),
164
+ path: z.object({
165
+ cwd: z.string(),
166
+ root: z.string(),
167
+ }),
168
+ cost: z.number(),
169
+ summary: z.boolean().optional(),
170
+ tokens: z.object({
171
+ input: z.number(),
172
+ output: z.number(),
173
+ reasoning: z.number(),
174
+ cache: z.object({
175
+ read: z.number(),
176
+ write: z.number(),
177
+ }),
178
+ }),
179
+ })
180
+ .optional(),
181
+ snapshot: z.string().optional(),
182
+ })
183
+ .meta({ ref: "MessageMetadata" }),
184
+ })
185
+ .meta({
186
+ ref: "Message",
187
+ })
188
+ export type Info = z.infer<typeof Info>
189
+ }
@@ -0,0 +1,356 @@
1
+ import type { ModelsDev } from "../provider/models"
2
+ import { MessageV2 } from "./message-v2"
3
+ import { type StreamTextResult, type Tool as AITool, APICallError } from "ai"
4
+ import { Log } from "../util/log"
5
+ import { Identifier } from "../id/id"
6
+ import { Session } from "."
7
+ import { Agent } from "../agent/agent"
8
+ // Permission system removed - no restrictions
9
+ import { Snapshot } from "../snapshot"
10
+ import { SessionSummary } from "./summary"
11
+ import { Bus } from "../bus"
12
+ import { SessionRetry } from "./retry"
13
+ import { SessionStatus } from "./status"
14
+
15
+ export namespace SessionProcessor {
16
+ const DOOM_LOOP_THRESHOLD = 3
17
+ const log = Log.create({ service: "session.processor" })
18
+
19
+ export type Info = Awaited<ReturnType<typeof create>>
20
+ export type Result = Awaited<ReturnType<Info["process"]>>
21
+
22
+ export function create(input: {
23
+ assistantMessage: MessageV2.Assistant
24
+ sessionID: string
25
+ providerID: string
26
+ model: ModelsDev.Model
27
+ abort: AbortSignal
28
+ }) {
29
+ const toolcalls: Record<string, MessageV2.ToolPart> = {}
30
+ let snapshot: string | undefined
31
+ let blocked = false
32
+ let attempt = 0
33
+
34
+ const result = {
35
+ get message() {
36
+ return input.assistantMessage
37
+ },
38
+ partFromToolCall(toolCallID: string) {
39
+ return toolcalls[toolCallID]
40
+ },
41
+ async process(fn: () => StreamTextResult<Record<string, AITool>, never>) {
42
+ log.info("process")
43
+ while (true) {
44
+ try {
45
+ let currentText: MessageV2.TextPart | undefined
46
+ let reasoningMap: Record<string, MessageV2.ReasoningPart> = {}
47
+ const stream = fn()
48
+
49
+ for await (const value of stream.fullStream) {
50
+ input.abort.throwIfAborted()
51
+ switch (value.type) {
52
+ case "start":
53
+ SessionStatus.set(input.sessionID, { type: "busy" })
54
+ break
55
+
56
+ case "reasoning-start":
57
+ if (value.id in reasoningMap) {
58
+ continue
59
+ }
60
+ reasoningMap[value.id] = {
61
+ id: Identifier.ascending("part"),
62
+ messageID: input.assistantMessage.id,
63
+ sessionID: input.assistantMessage.sessionID,
64
+ type: "reasoning",
65
+ text: "",
66
+ time: {
67
+ start: Date.now(),
68
+ },
69
+ metadata: value.providerMetadata,
70
+ }
71
+ break
72
+
73
+ case "reasoning-delta":
74
+ if (value.id in reasoningMap) {
75
+ const part = reasoningMap[value.id]
76
+ part.text += value.text
77
+ if (value.providerMetadata) part.metadata = value.providerMetadata
78
+ if (part.text) await Session.updatePart({ part, delta: value.text })
79
+ }
80
+ break
81
+
82
+ case "reasoning-end":
83
+ if (value.id in reasoningMap) {
84
+ const part = reasoningMap[value.id]
85
+ part.text = part.text.trimEnd()
86
+
87
+ part.time = {
88
+ ...part.time,
89
+ end: Date.now(),
90
+ }
91
+ if (value.providerMetadata) part.metadata = value.providerMetadata
92
+ await Session.updatePart(part)
93
+ delete reasoningMap[value.id]
94
+ }
95
+ break
96
+
97
+ case "tool-input-start":
98
+ const part = await Session.updatePart({
99
+ id: toolcalls[value.id]?.id ?? Identifier.ascending("part"),
100
+ messageID: input.assistantMessage.id,
101
+ sessionID: input.assistantMessage.sessionID,
102
+ type: "tool",
103
+ tool: value.toolName,
104
+ callID: value.id,
105
+ state: {
106
+ status: "pending",
107
+ input: {},
108
+ raw: "",
109
+ },
110
+ })
111
+ toolcalls[value.id] = part as MessageV2.ToolPart
112
+ break
113
+
114
+ case "tool-input-delta":
115
+ break
116
+
117
+ case "tool-input-end":
118
+ break
119
+
120
+ case "tool-call": {
121
+ const match = toolcalls[value.toolCallId]
122
+ if (match) {
123
+ const part = await Session.updatePart({
124
+ ...match,
125
+ tool: value.toolName,
126
+ state: {
127
+ status: "running",
128
+ input: value.input,
129
+ time: {
130
+ start: Date.now(),
131
+ },
132
+ },
133
+ metadata: value.providerMetadata,
134
+ })
135
+ toolcalls[value.toolCallId] = part as MessageV2.ToolPart
136
+
137
+ const parts = await MessageV2.parts(input.assistantMessage.id)
138
+ const lastThree = parts.slice(-DOOM_LOOP_THRESHOLD)
139
+
140
+ if (
141
+ lastThree.length === DOOM_LOOP_THRESHOLD &&
142
+ lastThree.every(
143
+ (p) =>
144
+ p.type === "tool" &&
145
+ p.tool === value.toolName &&
146
+ p.state.status !== "pending" &&
147
+ JSON.stringify(p.state.input) === JSON.stringify(value.input),
148
+ )
149
+ ) {
150
+ // Permission system removed - doom loop detection disabled
151
+ }
152
+ }
153
+ break
154
+ }
155
+ case "tool-result": {
156
+ const match = toolcalls[value.toolCallId]
157
+ if (match && match.state.status === "running") {
158
+ await Session.updatePart({
159
+ ...match,
160
+ state: {
161
+ status: "completed",
162
+ input: value.input,
163
+ output: value.output.output,
164
+ metadata: value.output.metadata,
165
+ title: value.output.title,
166
+ time: {
167
+ start: match.state.time.start,
168
+ end: Date.now(),
169
+ },
170
+ attachments: value.output.attachments,
171
+ },
172
+ })
173
+
174
+ delete toolcalls[value.toolCallId]
175
+ }
176
+ break
177
+ }
178
+
179
+ case "tool-error": {
180
+ const match = toolcalls[value.toolCallId]
181
+ if (match && match.state.status === "running") {
182
+ await Session.updatePart({
183
+ ...match,
184
+ state: {
185
+ status: "error",
186
+ input: value.input,
187
+ error: (value.error as any).toString(),
188
+ metadata: undefined,
189
+ time: {
190
+ start: match.state.time.start,
191
+ end: Date.now(),
192
+ },
193
+ },
194
+ })
195
+
196
+ // Permission system removed
197
+ delete toolcalls[value.toolCallId]
198
+ }
199
+ break
200
+ }
201
+ case "error":
202
+ throw value.error
203
+
204
+ case "start-step":
205
+ snapshot = await Snapshot.track()
206
+ await Session.updatePart({
207
+ id: Identifier.ascending("part"),
208
+ messageID: input.assistantMessage.id,
209
+ sessionID: input.sessionID,
210
+ snapshot,
211
+ type: "step-start",
212
+ })
213
+ break
214
+
215
+ case "finish-step":
216
+ const usage = Session.getUsage({
217
+ model: input.model,
218
+ usage: value.usage,
219
+ metadata: value.providerMetadata,
220
+ })
221
+ input.assistantMessage.finish = value.finishReason
222
+ input.assistantMessage.cost += usage.cost
223
+ input.assistantMessage.tokens = usage.tokens
224
+ await Session.updatePart({
225
+ id: Identifier.ascending("part"),
226
+ reason: value.finishReason,
227
+ snapshot: await Snapshot.track(),
228
+ messageID: input.assistantMessage.id,
229
+ sessionID: input.assistantMessage.sessionID,
230
+ type: "step-finish",
231
+ tokens: usage.tokens,
232
+ cost: usage.cost,
233
+ })
234
+ await Session.updateMessage(input.assistantMessage)
235
+ if (snapshot) {
236
+ const patch = await Snapshot.patch(snapshot)
237
+ if (patch.files.length) {
238
+ await Session.updatePart({
239
+ id: Identifier.ascending("part"),
240
+ messageID: input.assistantMessage.id,
241
+ sessionID: input.sessionID,
242
+ type: "patch",
243
+ hash: patch.hash,
244
+ files: patch.files,
245
+ })
246
+ }
247
+ snapshot = undefined
248
+ }
249
+ SessionSummary.summarize({
250
+ sessionID: input.sessionID,
251
+ messageID: input.assistantMessage.parentID,
252
+ })
253
+ break
254
+
255
+ case "text-start":
256
+ currentText = {
257
+ id: Identifier.ascending("part"),
258
+ messageID: input.assistantMessage.id,
259
+ sessionID: input.assistantMessage.sessionID,
260
+ type: "text",
261
+ text: "",
262
+ time: {
263
+ start: Date.now(),
264
+ },
265
+ metadata: value.providerMetadata,
266
+ }
267
+ break
268
+
269
+ case "text-delta":
270
+ if (currentText) {
271
+ currentText.text += value.text
272
+ if (value.providerMetadata) currentText.metadata = value.providerMetadata
273
+ if (currentText.text)
274
+ await Session.updatePart({
275
+ part: currentText,
276
+ delta: value.text,
277
+ })
278
+ }
279
+ break
280
+
281
+ case "text-end":
282
+ if (currentText) {
283
+ currentText.text = currentText.text.trimEnd()
284
+ currentText.time = {
285
+ start: Date.now(),
286
+ end: Date.now(),
287
+ }
288
+ if (value.providerMetadata) currentText.metadata = value.providerMetadata
289
+ await Session.updatePart(currentText)
290
+ }
291
+ currentText = undefined
292
+ break
293
+
294
+ case "finish":
295
+ input.assistantMessage.time.completed = Date.now()
296
+ await Session.updateMessage(input.assistantMessage)
297
+ break
298
+
299
+ default:
300
+ log.info("unhandled", {
301
+ ...value,
302
+ })
303
+ continue
304
+ }
305
+ }
306
+ } catch (e) {
307
+ log.error("process", {
308
+ error: e,
309
+ })
310
+ const error = MessageV2.fromError(e, { providerID: input.providerID })
311
+ if (error?.name === "APIError" && error.data.isRetryable) {
312
+ attempt++
313
+ const delay = SessionRetry.delay(error, attempt)
314
+ SessionStatus.set(input.sessionID, {
315
+ type: "retry",
316
+ attempt,
317
+ message: error.data.message,
318
+ next: Date.now() + delay,
319
+ })
320
+ await SessionRetry.sleep(delay, input.abort).catch(() => {})
321
+ continue
322
+ }
323
+ input.assistantMessage.error = error
324
+ Bus.publish(Session.Event.Error, {
325
+ sessionID: input.assistantMessage.sessionID,
326
+ error: input.assistantMessage.error,
327
+ })
328
+ }
329
+ const p = await MessageV2.parts(input.assistantMessage.id)
330
+ for (const part of p) {
331
+ if (part.type === "tool" && part.state.status !== "completed" && part.state.status !== "error") {
332
+ await Session.updatePart({
333
+ ...part,
334
+ state: {
335
+ ...part.state,
336
+ status: "error",
337
+ error: "Tool execution aborted",
338
+ time: {
339
+ start: Date.now(),
340
+ end: Date.now(),
341
+ },
342
+ },
343
+ })
344
+ }
345
+ }
346
+ input.assistantMessage.time.completed = Date.now()
347
+ await Session.updateMessage(input.assistantMessage)
348
+ if (blocked) return "stop"
349
+ if (input.assistantMessage.error) return "stop"
350
+ return "continue"
351
+ }
352
+ },
353
+ }
354
+ return result
355
+ }
356
+ }