clanka 0.0.26 → 0.0.27
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/Agent.d.ts +5 -4
- package/dist/Agent.d.ts.map +1 -1
- package/dist/Agent.js.map +1 -1
- package/dist/Agent.test.js +7 -8
- package/dist/Agent.test.js.map +1 -1
- package/dist/AgentTools.d.ts +274 -2
- package/dist/AgentTools.d.ts.map +1 -1
- package/dist/AgentTools.js +37 -2
- package/dist/AgentTools.js.map +1 -1
- package/dist/ApplyPatch.d.ts.map +1 -1
- package/dist/ApplyPatch.js +6 -2
- package/dist/ApplyPatch.js.map +1 -1
- package/dist/ApplyPatch.test.js +39 -0
- package/dist/ApplyPatch.test.js.map +1 -1
- package/dist/Codex.d.ts +1 -1
- package/dist/CodexAuth.d.ts +4 -4
- package/dist/Copilot.d.ts +1 -1
- package/dist/CopilotAuth.d.ts +3 -3
- package/dist/ExaSearch.d.ts +37 -0
- package/dist/ExaSearch.d.ts.map +1 -0
- package/dist/ExaSearch.js +56 -0
- package/dist/ExaSearch.js.map +1 -0
- package/dist/McpClient.d.ts +35 -0
- package/dist/McpClient.d.ts.map +1 -0
- package/dist/McpClient.js +51 -0
- package/dist/McpClient.js.map +1 -0
- package/dist/WebToMarkdown.d.ts +22 -0
- package/dist/WebToMarkdown.d.ts.map +1 -0
- package/dist/WebToMarkdown.js +66 -0
- package/dist/WebToMarkdown.js.map +1 -0
- package/package.json +13 -10
- package/src/Agent.test.ts +15 -9
- package/src/Agent.ts +11 -5
- package/src/AgentTools.ts +49 -1
- package/src/ApplyPatch.test.ts +44 -0
- package/src/ApplyPatch.ts +6 -2
- package/src/ExaSearch.ts +78 -0
- package/src/McpClient.ts +81 -0
- package/src/WebToMarkdown.ts +87 -0
- package/dist/AgentTools.test.d.ts +0 -2
- package/dist/AgentTools.test.d.ts.map +0 -1
- package/dist/AgentTools.test.js +0 -714
- package/dist/AgentTools.test.js.map +0 -1
- package/src/AgentTools.test.ts +0 -954
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
import { Effect, Layer, ServiceMap } from "effect";
|
|
5
|
+
import { HttpClient, HttpClientError } from "effect/unstable/http";
|
|
6
|
+
import TurndownService from "turndown";
|
|
7
|
+
/**
|
|
8
|
+
* @since 1.0.0
|
|
9
|
+
* @category Services
|
|
10
|
+
*/
|
|
11
|
+
export class WebToMarkdown extends ServiceMap.Service()("clanka/WebToMarkdown") {
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* @since 1.0.0
|
|
15
|
+
* @category Layers
|
|
16
|
+
*/
|
|
17
|
+
export const layer = Layer.effect(WebToMarkdown, Effect.gen(function* () {
|
|
18
|
+
const client = (yield* HttpClient.HttpClient).pipe(HttpClient.filterStatusOk, HttpClient.retryTransient({
|
|
19
|
+
times: 3,
|
|
20
|
+
}));
|
|
21
|
+
const toRemove = new Set([
|
|
22
|
+
"head",
|
|
23
|
+
"footer",
|
|
24
|
+
"header",
|
|
25
|
+
"script",
|
|
26
|
+
"style",
|
|
27
|
+
"meta",
|
|
28
|
+
"link",
|
|
29
|
+
"noscript",
|
|
30
|
+
"iframe",
|
|
31
|
+
"object",
|
|
32
|
+
"embed",
|
|
33
|
+
"svg",
|
|
34
|
+
"canvas",
|
|
35
|
+
"audio",
|
|
36
|
+
"video",
|
|
37
|
+
"source",
|
|
38
|
+
"track",
|
|
39
|
+
"map",
|
|
40
|
+
"area",
|
|
41
|
+
"base",
|
|
42
|
+
"form",
|
|
43
|
+
"input",
|
|
44
|
+
"textarea",
|
|
45
|
+
"button",
|
|
46
|
+
"select",
|
|
47
|
+
"option",
|
|
48
|
+
"optgroup",
|
|
49
|
+
"datalist",
|
|
50
|
+
"keygen",
|
|
51
|
+
"output",
|
|
52
|
+
"progress",
|
|
53
|
+
"meter",
|
|
54
|
+
]);
|
|
55
|
+
const turndown = new TurndownService().remove((node) => toRemove.has(node.nodeName.toLowerCase()));
|
|
56
|
+
const convertHtml = Effect.fn("WebToMarkdown.convertHtml")((html) => Effect.sync(() => turndown.turndown(html)));
|
|
57
|
+
return WebToMarkdown.of({
|
|
58
|
+
convertHtml,
|
|
59
|
+
convertUrl: Effect.fn("WebToMarkdown.convertUrl")(function* (url) {
|
|
60
|
+
const response = yield* client.get(url);
|
|
61
|
+
const html = yield* response.text;
|
|
62
|
+
return turndown.turndown(html);
|
|
63
|
+
}),
|
|
64
|
+
});
|
|
65
|
+
}));
|
|
66
|
+
//# sourceMappingURL=WebToMarkdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebToMarkdown.js","sourceRoot":"","sources":["../src/WebToMarkdown.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAClD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAClE,OAAO,eAAe,MAAM,UAAU,CAAA;AAEtC;;;GAGG;AACH,MAAM,OAAO,aAAc,SAAQ,UAAU,CAAC,OAAO,EAQlD,CAAC,sBAAsB,CAAC;CAAG;AAE9B;;;GAGG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAC/B,aAAa,EACb,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,IAAI,CAChD,UAAU,CAAC,cAAc,EACzB,UAAU,CAAC,cAAc,CAAC;QACxB,KAAK,EAAE,CAAC;KACT,CAAC,CACH,CAAA;IAED,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC;QACvB,MAAM;QACN,QAAQ;QACR,QAAQ;QACR,QAAQ;QACR,OAAO;QACP,MAAM;QACN,MAAM;QACN,UAAU;QACV,QAAQ;QACR,QAAQ;QACR,OAAO;QACP,KAAK;QACL,QAAQ;QACR,OAAO;QACP,OAAO;QACP,QAAQ;QACR,OAAO;QACP,KAAK;QACL,MAAM;QACN,MAAM;QACN,MAAM;QACN,OAAO;QACP,UAAU;QACV,QAAQ;QACR,QAAQ;QACR,QAAQ;QACR,UAAU;QACV,UAAU;QACV,QAAQ;QACR,QAAQ;QACR,UAAU;QACV,OAAO;KACR,CAAC,CAAA;IACF,MAAM,QAAQ,GAAG,IAAI,eAAe,EAAE,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CACrD,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAC1C,CAAA;IAED,MAAM,WAAW,GAAG,MAAM,CAAC,EAAE,CAAC,2BAA2B,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAClE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAC3C,CAAA;IAED,OAAO,aAAa,CAAC,EAAE,CAAC;QACtB,WAAW;QACX,UAAU,EAAE,MAAM,CAAC,EAAE,CAAC,0BAA0B,CAAC,CAAC,QAAQ,CAAC,EAAE,GAAG;YAC9D,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YACvC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAA;YACjC,OAAO,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QAChC,CAAC,CAAC;KACH,CAAC,CAAA;AACJ,CAAC,CAAC,CACH,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clanka",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.27",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -22,27 +22,30 @@
|
|
|
22
22
|
"url": "https://github.com/tim-smart/clanka.git"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
25
26
|
"@vscode/ripgrep": "^1.17.0",
|
|
26
27
|
"chalk": "^5.6.2",
|
|
27
|
-
"glob": "^13.0.6"
|
|
28
|
+
"glob": "^13.0.6",
|
|
29
|
+
"turndown": "^7.2.2"
|
|
28
30
|
},
|
|
29
31
|
"peerDependencies": {
|
|
30
|
-
"@effect/ai-openai": "4.0.0-beta.
|
|
31
|
-
"@effect/ai-openai-compat": "4.0.0-beta.
|
|
32
|
-
"effect": "4.0.0-beta.
|
|
32
|
+
"@effect/ai-openai": "4.0.0-beta.31",
|
|
33
|
+
"@effect/ai-openai-compat": "4.0.0-beta.31",
|
|
34
|
+
"effect": "4.0.0-beta.31"
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"@changesets/changelog-github": "^0.5.2",
|
|
36
38
|
"@changesets/cli": "^2.29.8",
|
|
37
|
-
"@effect/ai-openai": "4.0.0-beta.
|
|
38
|
-
"@effect/ai-openai-compat": "4.0.0-beta.
|
|
39
|
+
"@effect/ai-openai": "4.0.0-beta.31",
|
|
40
|
+
"@effect/ai-openai-compat": "4.0.0-beta.31",
|
|
39
41
|
"@effect/language-service": "^0.75.1",
|
|
40
|
-
"@effect/platform-node": "4.0.0-beta.
|
|
41
|
-
"@effect/vitest": "4.0.0-beta.
|
|
42
|
+
"@effect/platform-node": "4.0.0-beta.31",
|
|
43
|
+
"@effect/vitest": "4.0.0-beta.31",
|
|
42
44
|
"@linear/sdk": "^75.0.0",
|
|
43
45
|
"@types/node": "^25.3.5",
|
|
46
|
+
"@types/turndown": "^5.0.6",
|
|
44
47
|
"@typescript/native-preview": "7.0.0-dev.20260219.1",
|
|
45
|
-
"effect": "4.0.0-beta.
|
|
48
|
+
"effect": "4.0.0-beta.31",
|
|
46
49
|
"husky": "^9.1.7",
|
|
47
50
|
"lint-staged": "^16.2.7",
|
|
48
51
|
"oxlint": "^1.49.0",
|
package/src/Agent.test.ts
CHANGED
|
@@ -2,10 +2,13 @@ import { NodeServices } from "@effect/platform-node"
|
|
|
2
2
|
import { Effect, Layer, Stream } from "effect"
|
|
3
3
|
import { describe, it } from "@effect/vitest"
|
|
4
4
|
import { expect } from "vitest"
|
|
5
|
-
import { AgentModelConfig,
|
|
5
|
+
import { AgentModelConfig, make } from "./Agent.ts"
|
|
6
6
|
import { pretty } from "./OutputFormatter.ts"
|
|
7
7
|
import { LanguageModel, Prompt } from "effect/unstable/ai"
|
|
8
8
|
import * as Model from "effect/unstable/ai/Model"
|
|
9
|
+
import { Executor } from "./Executor.ts"
|
|
10
|
+
import { ToolkitRenderer } from "./ToolkitRenderer.ts"
|
|
11
|
+
import { AgentToolHandlersTest } from "./AgentTools.ts"
|
|
9
12
|
|
|
10
13
|
const usage = {
|
|
11
14
|
inputTokens: {
|
|
@@ -140,14 +143,17 @@ describe("Agent", () => {
|
|
|
140
143
|
"root summary: child summary: grandchild summary",
|
|
141
144
|
)
|
|
142
145
|
}).pipe(
|
|
143
|
-
Effect.provide(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
Effect.provide(
|
|
147
|
+
Layer.mergeAll(
|
|
148
|
+
AgentToolHandlersTest,
|
|
149
|
+
TestModel,
|
|
150
|
+
AgentModelConfig.layer({
|
|
151
|
+
supportsNoTools: true,
|
|
152
|
+
}),
|
|
153
|
+
Executor.layer,
|
|
154
|
+
ToolkitRenderer.layer,
|
|
155
|
+
).pipe(Layer.provideMerge(NodeServices.layer)),
|
|
156
|
+
),
|
|
151
157
|
),
|
|
152
158
|
)
|
|
153
159
|
})
|
package/src/Agent.ts
CHANGED
|
@@ -36,6 +36,7 @@ import { ToolkitRenderer } from "./ToolkitRenderer.ts"
|
|
|
36
36
|
import { ModelName, ProviderName } from "effect/unstable/ai/Model"
|
|
37
37
|
import { type StreamPart } from "effect/unstable/ai/Response"
|
|
38
38
|
import type { ChildProcessSpawner } from "effect/unstable/process"
|
|
39
|
+
import type { HttpClient } from "effect/unstable/http"
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
42
|
* @since 1.0.0
|
|
@@ -62,7 +63,8 @@ export interface Agent {
|
|
|
62
63
|
* @category Constructors
|
|
63
64
|
*/
|
|
64
65
|
export const make: <
|
|
65
|
-
|
|
66
|
+
// oxlint-disable-next-line typescript/no-explicit-any
|
|
67
|
+
Toolkit extends Toolkit.Toolkit<any> = never,
|
|
66
68
|
SE = never,
|
|
67
69
|
SR = never,
|
|
68
70
|
>(options: {
|
|
@@ -82,7 +84,7 @@ export const make: <
|
|
|
82
84
|
}) => string)
|
|
83
85
|
| undefined
|
|
84
86
|
/** Additional tools to provide to the agent */
|
|
85
|
-
readonly tools?: Toolkit
|
|
87
|
+
readonly tools?: Toolkit | undefined
|
|
86
88
|
/** Layer to use for subagents */
|
|
87
89
|
readonly subagentModel?:
|
|
88
90
|
| Layer.Layer<
|
|
@@ -102,9 +104,10 @@ export const make: <
|
|
|
102
104
|
| ProviderName
|
|
103
105
|
| ModelName
|
|
104
106
|
| ToolkitRenderer
|
|
105
|
-
|
|
|
107
|
+
| (Toolkit extends Toolkit.Toolkit<infer T>
|
|
108
|
+
? Tool.HandlersFor<T> | Tool.HandlerServices<T[keyof T]>
|
|
109
|
+
: never)
|
|
106
110
|
| Tool.HandlersFor<typeof AgentTools.tools>
|
|
107
|
-
| Tool.HandlerServices<Tools[keyof Tools]>
|
|
108
111
|
| SR
|
|
109
112
|
> = Effect.fnUntraced(function* (options: {
|
|
110
113
|
readonly directory: string
|
|
@@ -628,7 +631,10 @@ export class AgentModelConfig extends ServiceMap.Reference<{
|
|
|
628
631
|
export const layerServices: Layer.Layer<
|
|
629
632
|
Tool.HandlersFor<typeof AgentTools.tools> | Executor | ToolkitRenderer,
|
|
630
633
|
never,
|
|
631
|
-
FileSystem.FileSystem
|
|
634
|
+
| FileSystem.FileSystem
|
|
635
|
+
| Path.Path
|
|
636
|
+
| ChildProcessSpawner.ChildProcessSpawner
|
|
637
|
+
| HttpClient.HttpClient
|
|
632
638
|
> = Layer.mergeAll(AgentToolHandlers, Executor.layer, ToolkitRenderer.layer)
|
|
633
639
|
|
|
634
640
|
/**
|
package/src/AgentTools.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
Deferred,
|
|
8
8
|
Effect,
|
|
9
9
|
FileSystem,
|
|
10
|
+
Layer,
|
|
10
11
|
Path,
|
|
11
12
|
pipe,
|
|
12
13
|
Schema,
|
|
@@ -17,6 +18,8 @@ import { Tool, Toolkit } from "effect/unstable/ai"
|
|
|
17
18
|
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
|
18
19
|
import * as Glob from "glob"
|
|
19
20
|
import { parsePatch, patchChunks } from "./ApplyPatch.ts"
|
|
21
|
+
import * as ExaSearch from "./ExaSearch.ts"
|
|
22
|
+
import * as WebToMarkdown from "./WebToMarkdown.ts"
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
25
|
* @since 1.0.0
|
|
@@ -175,6 +178,18 @@ export const AgentTools = Toolkit.make(
|
|
|
175
178
|
success: Schema.String,
|
|
176
179
|
dependencies: [SubagentContext],
|
|
177
180
|
}),
|
|
181
|
+
Tool.make("webSearch", {
|
|
182
|
+
description: "Search the web for recent information.",
|
|
183
|
+
parameters: ExaSearch.ExaSearchOptions,
|
|
184
|
+
success: Schema.String,
|
|
185
|
+
}),
|
|
186
|
+
Tool.make("fetchMarkdown", {
|
|
187
|
+
description: "Fetch a web page and convert it to markdown.",
|
|
188
|
+
parameters: Schema.String.annotate({
|
|
189
|
+
identifier: "url",
|
|
190
|
+
}),
|
|
191
|
+
success: Schema.String,
|
|
192
|
+
}),
|
|
178
193
|
Tool.make("sleep", {
|
|
179
194
|
description: "Sleep for a specified number of milliseconds",
|
|
180
195
|
parameters: Schema.Finite.annotate({
|
|
@@ -195,11 +210,13 @@ export const AgentTools = Toolkit.make(
|
|
|
195
210
|
* @since 1.0.0
|
|
196
211
|
* @category Toolkit
|
|
197
212
|
*/
|
|
198
|
-
export const
|
|
213
|
+
export const AgentToolHandlersNoDeps = AgentTools.toLayer(
|
|
199
214
|
Effect.gen(function* () {
|
|
200
215
|
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
|
|
201
216
|
const fs = yield* FileSystem.FileSystem
|
|
202
217
|
const pathService = yield* Path.Path
|
|
218
|
+
const webSearch = yield* ExaSearch.ExaSearch
|
|
219
|
+
const fetchMarkdown = yield* WebToMarkdown.WebToMarkdown
|
|
203
220
|
|
|
204
221
|
const execute = Effect.fn(function* (command: ChildProcess.Command) {
|
|
205
222
|
const handle = yield* spawner.spawn(command)
|
|
@@ -356,6 +373,18 @@ export const AgentToolHandlers = AgentTools.toLayer(
|
|
|
356
373
|
})
|
|
357
374
|
return yield* execute(cmd)
|
|
358
375
|
}, Effect.orDie),
|
|
376
|
+
webSearch: Effect.fn("AgentTools.webSearch")(function* (options) {
|
|
377
|
+
yield* Effect.logInfo(`Calling "webSearch"`).pipe(
|
|
378
|
+
Effect.annotateLogs(options),
|
|
379
|
+
)
|
|
380
|
+
return yield* webSearch.search(options)
|
|
381
|
+
}, Effect.orDie),
|
|
382
|
+
fetchMarkdown: Effect.fn("AgentTools.fetchMarkdown")(function* (url) {
|
|
383
|
+
yield* Effect.logInfo(`Calling "fetchMarkdown"`).pipe(
|
|
384
|
+
Effect.annotateLogs({ url }),
|
|
385
|
+
)
|
|
386
|
+
return yield* fetchMarkdown.convertUrl(url)
|
|
387
|
+
}, Effect.orDie),
|
|
359
388
|
sleep: Effect.fn("AgentTools.sleep")(function* (ms) {
|
|
360
389
|
yield* Effect.logInfo(`Calling "sleep" for ${ms}ms`)
|
|
361
390
|
return yield* Effect.sleep(ms)
|
|
@@ -507,6 +536,25 @@ export const AgentToolHandlers = AgentTools.toLayer(
|
|
|
507
536
|
}),
|
|
508
537
|
)
|
|
509
538
|
|
|
539
|
+
/**
|
|
540
|
+
* @since 1.0.0
|
|
541
|
+
* @category Layers
|
|
542
|
+
*/
|
|
543
|
+
export const AgentToolHandlers = AgentToolHandlersNoDeps.pipe(
|
|
544
|
+
Layer.provide([ExaSearch.layer, WebToMarkdown.layer]),
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* @since 1.0.0
|
|
549
|
+
* @category Layers
|
|
550
|
+
*/
|
|
551
|
+
export const AgentToolHandlersTest = AgentToolHandlersNoDeps.pipe(
|
|
552
|
+
Layer.provide([
|
|
553
|
+
Layer.mock(ExaSearch.ExaSearch)({}),
|
|
554
|
+
Layer.mock(WebToMarkdown.WebToMarkdown)({}),
|
|
555
|
+
]),
|
|
556
|
+
)
|
|
557
|
+
|
|
510
558
|
class ApplyPatchError extends Data.TaggedClass("ApplyPatchError")<{
|
|
511
559
|
readonly message: string
|
|
512
560
|
}> {}
|
package/src/ApplyPatch.test.ts
CHANGED
|
@@ -28,6 +28,50 @@ describe("patchContent", () => {
|
|
|
28
28
|
).toBe("alpha\nbeta\nomega\n")
|
|
29
29
|
})
|
|
30
30
|
|
|
31
|
+
it("parses wrapped patches without an end marker at EOF", () => {
|
|
32
|
+
expect(
|
|
33
|
+
parsePatch(
|
|
34
|
+
[
|
|
35
|
+
"*** Begin Patch",
|
|
36
|
+
"*** Update File: src/ExaSearch.ts",
|
|
37
|
+
"@@",
|
|
38
|
+
" export class ExaSearch extends ServiceMap.Service<",
|
|
39
|
+
" ExaSearch,",
|
|
40
|
+
" {",
|
|
41
|
+
"- search(query: string): Effect.Effect<Array<SearchResponse<{}>>, ExaError>",
|
|
42
|
+
"+ search(query: string): Effect.Effect<SearchResponse<{}>, ExaError>",
|
|
43
|
+
" }",
|
|
44
|
+
' >()("clanka/ExaSearch") {}',
|
|
45
|
+
].join("\n"),
|
|
46
|
+
),
|
|
47
|
+
).toEqual([
|
|
48
|
+
{
|
|
49
|
+
type: "update",
|
|
50
|
+
path: "src/ExaSearch.ts",
|
|
51
|
+
chunks: [
|
|
52
|
+
{
|
|
53
|
+
old: [
|
|
54
|
+
"export class ExaSearch extends ServiceMap.Service<",
|
|
55
|
+
" ExaSearch,",
|
|
56
|
+
" {",
|
|
57
|
+
" search(query: string): Effect.Effect<Array<SearchResponse<{}>>, ExaError>",
|
|
58
|
+
" }",
|
|
59
|
+
'>()("clanka/ExaSearch") {}',
|
|
60
|
+
],
|
|
61
|
+
next: [
|
|
62
|
+
"export class ExaSearch extends ServiceMap.Service<",
|
|
63
|
+
" ExaSearch,",
|
|
64
|
+
" {",
|
|
65
|
+
" search(query: string): Effect.Effect<SearchResponse<{}>, ExaError>",
|
|
66
|
+
" }",
|
|
67
|
+
'>()("clanka/ExaSearch") {}',
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
])
|
|
73
|
+
})
|
|
74
|
+
|
|
31
75
|
it("parses multi-file wrapped patches", () => {
|
|
32
76
|
expect(
|
|
33
77
|
parsePatch(
|
package/src/ApplyPatch.ts
CHANGED
|
@@ -55,8 +55,12 @@ const fail = (message: string): never => {
|
|
|
55
55
|
const locate = (text: string) => {
|
|
56
56
|
const lines = text.split("\n")
|
|
57
57
|
const begin = lines.findIndex((line) => line === BEGIN)
|
|
58
|
-
const
|
|
59
|
-
if (begin === -1
|
|
58
|
+
const explicitEnd = lines.findIndex((line) => line === END)
|
|
59
|
+
if (begin === -1) {
|
|
60
|
+
fail("Invalid patch format: missing Begin/End markers")
|
|
61
|
+
}
|
|
62
|
+
const end = explicitEnd === -1 ? lines.length : explicitEnd
|
|
63
|
+
if (begin >= end) {
|
|
60
64
|
fail("Invalid patch format: missing Begin/End markers")
|
|
61
65
|
}
|
|
62
66
|
return {
|
package/src/ExaSearch.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
import { Effect, Layer, pipe, Schema, ServiceMap } from "effect"
|
|
5
|
+
import * as McpClient from "./McpClient.ts"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @since 1.0.0
|
|
9
|
+
* @category Services
|
|
10
|
+
*/
|
|
11
|
+
export class ExaSearch extends ServiceMap.Service<
|
|
12
|
+
ExaSearch,
|
|
13
|
+
{
|
|
14
|
+
search(
|
|
15
|
+
options: typeof ExaSearchOptions.Type,
|
|
16
|
+
): Effect.Effect<string, ExaError>
|
|
17
|
+
}
|
|
18
|
+
>()("clanka/ExaSearch") {}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @since 1.0.0
|
|
22
|
+
* @category Schemas
|
|
23
|
+
*/
|
|
24
|
+
export const ExaSearchOptions = Schema.Struct({
|
|
25
|
+
query: Schema.String,
|
|
26
|
+
numResults: Schema.optional(Schema.Number).annotate({
|
|
27
|
+
documentation: "The number of search results to return. Defaults to 3.",
|
|
28
|
+
}),
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
class ExaSearchResult extends Schema.Class<ExaSearchResult>("ExaSearchResult")({
|
|
32
|
+
type: Schema.Literal("text"),
|
|
33
|
+
text: Schema.String,
|
|
34
|
+
}) {}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @since 1.0.0
|
|
38
|
+
* @category Errors
|
|
39
|
+
*/
|
|
40
|
+
export class ExaError extends Schema.TaggedErrorClass<ExaError>()("ExaError", {
|
|
41
|
+
cause: Schema.Defect,
|
|
42
|
+
}) {}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @since 1.0.0
|
|
46
|
+
* @category Layers
|
|
47
|
+
*/
|
|
48
|
+
export const layer = Layer.effect(
|
|
49
|
+
ExaSearch,
|
|
50
|
+
Effect.gen(function* () {
|
|
51
|
+
const client = yield* McpClient.McpClient
|
|
52
|
+
|
|
53
|
+
yield* client.connect({ url: "https://mcp.exa.ai/mcp" }).pipe(Effect.orDie)
|
|
54
|
+
|
|
55
|
+
const decode = Schema.decodeUnknownEffect(
|
|
56
|
+
Schema.NonEmptyArray(ExaSearchResult),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return ExaSearch.of({
|
|
60
|
+
search: Effect.fn("ExaSearch.search")(
|
|
61
|
+
function* (options) {
|
|
62
|
+
const results = yield* pipe(
|
|
63
|
+
client.toolCall({
|
|
64
|
+
name: "web_search_exa",
|
|
65
|
+
arguments: {
|
|
66
|
+
query: options.query,
|
|
67
|
+
num_results: options.numResults ?? 3,
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
Effect.flatMap(decode),
|
|
71
|
+
)
|
|
72
|
+
return results[0].text
|
|
73
|
+
},
|
|
74
|
+
Effect.mapError((cause) => new ExaError({ cause })),
|
|
75
|
+
),
|
|
76
|
+
})
|
|
77
|
+
}),
|
|
78
|
+
).pipe(Layer.provide(McpClient.layer))
|
package/src/McpClient.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
import { Effect, Layer, Schema, ServiceMap } from "effect"
|
|
5
|
+
import { Client } from "@modelcontextprotocol/sdk/client"
|
|
6
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
|
|
7
|
+
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @since 1.0.0
|
|
11
|
+
* @category Services
|
|
12
|
+
*/
|
|
13
|
+
export class McpClient extends ServiceMap.Service<
|
|
14
|
+
McpClient,
|
|
15
|
+
{
|
|
16
|
+
connect(options: {
|
|
17
|
+
readonly url: string
|
|
18
|
+
}): Effect.Effect<void, McpClientError>
|
|
19
|
+
toolCall(options: {
|
|
20
|
+
readonly name: string
|
|
21
|
+
readonly arguments: Record<string, unknown>
|
|
22
|
+
}): Effect.Effect<unknown, McpClientError>
|
|
23
|
+
}
|
|
24
|
+
>()("clanka/McpClient") {}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @since 1.0.0
|
|
28
|
+
* @category Errors
|
|
29
|
+
*/
|
|
30
|
+
export class McpClientError extends Schema.TaggedErrorClass<McpClientError>()(
|
|
31
|
+
"McpClientError",
|
|
32
|
+
{
|
|
33
|
+
cause: Schema.Defect,
|
|
34
|
+
},
|
|
35
|
+
) {}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @since 1.0.0
|
|
39
|
+
* @category Layers
|
|
40
|
+
*/
|
|
41
|
+
export const layer = Layer.effect(
|
|
42
|
+
McpClient,
|
|
43
|
+
Effect.gen(function* () {
|
|
44
|
+
const client = yield* Effect.acquireRelease(
|
|
45
|
+
Effect.sync(
|
|
46
|
+
() =>
|
|
47
|
+
new Client({
|
|
48
|
+
name: "clanka",
|
|
49
|
+
version: "0.1.0",
|
|
50
|
+
}),
|
|
51
|
+
),
|
|
52
|
+
(client) => Effect.promise(() => client.close()),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
const connect = Effect.fn("McpClient.connect")(function* (options: {
|
|
56
|
+
readonly url: string
|
|
57
|
+
}) {
|
|
58
|
+
const transport = new StreamableHTTPClientTransport(new URL(options.url))
|
|
59
|
+
return yield* Effect.tryPromise({
|
|
60
|
+
try: (signal) => client.connect(transport as Transport, { signal }),
|
|
61
|
+
catch: (cause) => new McpClientError({ cause }),
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
return McpClient.of({
|
|
66
|
+
connect,
|
|
67
|
+
toolCall: Effect.fn("McpClient.toolCall")((options) =>
|
|
68
|
+
Effect.tryPromise({
|
|
69
|
+
try: async () => {
|
|
70
|
+
const response = await client.callTool({
|
|
71
|
+
name: options.name,
|
|
72
|
+
arguments: options.arguments,
|
|
73
|
+
})
|
|
74
|
+
return response.structuredContent ?? response.content
|
|
75
|
+
},
|
|
76
|
+
catch: (cause) => new McpClientError({ cause }),
|
|
77
|
+
}),
|
|
78
|
+
),
|
|
79
|
+
})
|
|
80
|
+
}),
|
|
81
|
+
)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @since 1.0.0
|
|
3
|
+
*/
|
|
4
|
+
import { Effect, Layer, ServiceMap } from "effect"
|
|
5
|
+
import { HttpClient, HttpClientError } from "effect/unstable/http"
|
|
6
|
+
import TurndownService from "turndown"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @since 1.0.0
|
|
10
|
+
* @category Services
|
|
11
|
+
*/
|
|
12
|
+
export class WebToMarkdown extends ServiceMap.Service<
|
|
13
|
+
WebToMarkdown,
|
|
14
|
+
{
|
|
15
|
+
convertHtml(html: string): Effect.Effect<string>
|
|
16
|
+
convertUrl(
|
|
17
|
+
url: string,
|
|
18
|
+
): Effect.Effect<string, HttpClientError.HttpClientError>
|
|
19
|
+
}
|
|
20
|
+
>()("clanka/WebToMarkdown") {}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @since 1.0.0
|
|
24
|
+
* @category Layers
|
|
25
|
+
*/
|
|
26
|
+
export const layer = Layer.effect(
|
|
27
|
+
WebToMarkdown,
|
|
28
|
+
Effect.gen(function* () {
|
|
29
|
+
const client = (yield* HttpClient.HttpClient).pipe(
|
|
30
|
+
HttpClient.filterStatusOk,
|
|
31
|
+
HttpClient.retryTransient({
|
|
32
|
+
times: 3,
|
|
33
|
+
}),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
const toRemove = new Set([
|
|
37
|
+
"head",
|
|
38
|
+
"footer",
|
|
39
|
+
"header",
|
|
40
|
+
"script",
|
|
41
|
+
"style",
|
|
42
|
+
"meta",
|
|
43
|
+
"link",
|
|
44
|
+
"noscript",
|
|
45
|
+
"iframe",
|
|
46
|
+
"object",
|
|
47
|
+
"embed",
|
|
48
|
+
"svg",
|
|
49
|
+
"canvas",
|
|
50
|
+
"audio",
|
|
51
|
+
"video",
|
|
52
|
+
"source",
|
|
53
|
+
"track",
|
|
54
|
+
"map",
|
|
55
|
+
"area",
|
|
56
|
+
"base",
|
|
57
|
+
"form",
|
|
58
|
+
"input",
|
|
59
|
+
"textarea",
|
|
60
|
+
"button",
|
|
61
|
+
"select",
|
|
62
|
+
"option",
|
|
63
|
+
"optgroup",
|
|
64
|
+
"datalist",
|
|
65
|
+
"keygen",
|
|
66
|
+
"output",
|
|
67
|
+
"progress",
|
|
68
|
+
"meter",
|
|
69
|
+
])
|
|
70
|
+
const turndown = new TurndownService().remove((node) =>
|
|
71
|
+
toRemove.has(node.nodeName.toLowerCase()),
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
const convertHtml = Effect.fn("WebToMarkdown.convertHtml")((html) =>
|
|
75
|
+
Effect.sync(() => turndown.turndown(html)),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return WebToMarkdown.of({
|
|
79
|
+
convertHtml,
|
|
80
|
+
convertUrl: Effect.fn("WebToMarkdown.convertUrl")(function* (url) {
|
|
81
|
+
const response = yield* client.get(url)
|
|
82
|
+
const html = yield* response.text
|
|
83
|
+
return turndown.turndown(html)
|
|
84
|
+
}),
|
|
85
|
+
})
|
|
86
|
+
}),
|
|
87
|
+
)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"AgentTools.test.d.ts","sourceRoot":"","sources":["../src/AgentTools.test.ts"],"names":[],"mappings":""}
|