clanka 0.2.71 → 0.3.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.
Files changed (57) hide show
  1. package/dist/Acp.d.ts +88 -0
  2. package/dist/Acp.d.ts.map +1 -0
  3. package/dist/Acp.js +387 -0
  4. package/dist/Acp.js.map +1 -0
  5. package/dist/Acp.test.d.ts +2 -0
  6. package/dist/Acp.test.d.ts.map +1 -0
  7. package/dist/Acp.test.js +179 -0
  8. package/dist/Acp.test.js.map +1 -0
  9. package/dist/AgentExecutor.d.ts +36 -36
  10. package/dist/AgentExecutor.d.ts.map +1 -1
  11. package/dist/AgentExecutor.js +5 -5
  12. package/dist/AgentExecutor.js.map +1 -1
  13. package/dist/AgentOutput.js +1 -1
  14. package/dist/AgentOutput.js.map +1 -1
  15. package/dist/ChunkRepo.js +1 -1
  16. package/dist/ChunkRepo.js.map +1 -1
  17. package/dist/CodeChunker.js +1 -1
  18. package/dist/CodeChunker.js.map +1 -1
  19. package/dist/CodexAuth.js +1 -1
  20. package/dist/CodexAuth.js.map +1 -1
  21. package/dist/CopilotAuth.js +1 -1
  22. package/dist/CopilotAuth.js.map +1 -1
  23. package/dist/DeviceCodeHandler.d.ts +2 -1
  24. package/dist/DeviceCodeHandler.d.ts.map +1 -1
  25. package/dist/DeviceCodeHandler.js +4 -0
  26. package/dist/DeviceCodeHandler.js.map +1 -1
  27. package/dist/ExaSearch.js +1 -1
  28. package/dist/ExaSearch.js.map +1 -1
  29. package/dist/McpClient.js +1 -1
  30. package/dist/McpClient.js.map +1 -1
  31. package/dist/SemanticSearch.d.ts +2 -2
  32. package/dist/SemanticSearch.d.ts.map +1 -1
  33. package/dist/SemanticSearch.js.map +1 -1
  34. package/dist/TypeBuilder.js +1 -1
  35. package/dist/TypeBuilder.js.map +1 -1
  36. package/dist/cli.js +40 -29
  37. package/dist/cli.js.map +1 -1
  38. package/dist/index.d.ts +4 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +4 -0
  41. package/dist/index.js.map +1 -1
  42. package/package.json +21 -21
  43. package/src/Acp.test.ts +288 -0
  44. package/src/Acp.ts +596 -0
  45. package/src/AgentExecutor.ts +18 -24
  46. package/src/AgentOutput.ts +1 -1
  47. package/src/ChunkRepo.ts +1 -1
  48. package/src/CodeChunker.ts +1 -1
  49. package/src/CodexAuth.ts +1 -1
  50. package/src/CopilotAuth.ts +1 -1
  51. package/src/DeviceCodeHandler.ts +8 -1
  52. package/src/ExaSearch.ts +1 -1
  53. package/src/McpClient.ts +1 -1
  54. package/src/SemanticSearch.ts +2 -2
  55. package/src/TypeBuilder.ts +1 -1
  56. package/src/cli.ts +100 -46
  57. package/src/index.ts +5 -0
@@ -1,5 +1,5 @@
1
1
  import * as Console from "effect/Console"
2
- import type * as Effect from "effect/Effect"
2
+ import * as Effect from "effect/Effect"
3
3
  import * as Layer from "effect/Layer"
4
4
  import * as Context from "effect/Context"
5
5
 
@@ -19,3 +19,10 @@ export const layerConsole = Layer.succeed(DeviceCodeHandler, {
19
19
  `Open ${options.verifyUrl} and enter code ${options.deviceCode}.`,
20
20
  ),
21
21
  })
22
+
23
+ export const layerLog = Layer.succeed(DeviceCodeHandler, {
24
+ onCode: (options) =>
25
+ Effect.logWarning(
26
+ `Open ${options.verifyUrl} and enter code ${options.deviceCode}.`,
27
+ ),
28
+ })
package/src/ExaSearch.ts CHANGED
@@ -42,7 +42,7 @@ class ExaSearchResult extends Schema.Class<ExaSearchResult>("ExaSearchResult")({
42
42
  * @since 1.0.0
43
43
  * @category Errors
44
44
  */
45
- export class ExaError extends Schema.TaggedErrorClass<ExaError>()("ExaError", {
45
+ export class ExaError extends Schema.TaggedError<ExaError>()("ExaError", {
46
46
  cause: Schema.Defect(),
47
47
  }) {}
48
48
 
package/src/McpClient.ts CHANGED
@@ -30,7 +30,7 @@ export class McpClient extends Context.Service<
30
30
  * @since 1.0.0
31
31
  * @category Errors
32
32
  */
33
- export class McpClientError extends Schema.TaggedErrorClass<McpClientError>()(
33
+ export class McpClientError extends Schema.TaggedError<McpClientError>()(
34
34
  "McpClientError",
35
35
  {
36
36
  cause: Schema.Defect(),
@@ -41,12 +41,12 @@ const resolveChunkConfig = (options: {
41
41
  })
42
42
 
43
43
  export const makeEmbeddingResolver = (
44
- resolver: EmbeddingModel.Service["resolver"],
44
+ resolver: EmbeddingModel.EmbeddingModel["resolver"],
45
45
  options: {
46
46
  readonly embeddingBatchSize?: number | undefined
47
47
  readonly embeddingRequestDelay?: Duration.Input | undefined
48
48
  },
49
- ): EmbeddingModel.Service["resolver"] =>
49
+ ): EmbeddingModel.EmbeddingModel["resolver"] =>
50
50
  resolver.pipe(
51
51
  RequestResolver.setDelay(
52
52
  options.embeddingRequestDelay ?? Duration.millis(50),
@@ -440,7 +440,7 @@ const stripOptionalTupleUndefined = (ast: AST.AST): AST.AST => {
440
440
  ? definedType
441
441
  : new AST.Union(
442
442
  definedTypes,
443
- ast.mode,
443
+ ast.options,
444
444
  ast.annotations,
445
445
  ast.checks,
446
446
  ast.encoding,
package/src/cli.ts CHANGED
@@ -7,6 +7,8 @@ import * as Flag from "effect/unstable/cli/Flag"
7
7
  import * as NodeServices from "@effect/platform-node/NodeServices"
8
8
  import * as NodeRuntime from "@effect/platform-node/NodeRuntime"
9
9
  import * as NodeSocket from "@effect/platform-node/NodeSocket"
10
+ import * as Acp from "./Acp.ts"
11
+ import * as AgentExecutor from "./AgentExecutor.ts"
10
12
  import * as Codex from "./Codex.ts"
11
13
  import * as Copilot from "./Copilot.ts"
12
14
  import * as Agent from "./Agent.ts"
@@ -15,17 +17,65 @@ import * as OutputFormatter from "./OutputFormatter.ts"
15
17
  import * as Stdio from "effect/Stdio"
16
18
  import { pipe } from "effect/Function"
17
19
  import * as Layer from "effect/Layer"
20
+ import * as Logger from "effect/Logger"
18
21
  import * as Path from "effect/Path"
22
+ import * as Schema from "effect/Schema"
23
+ import type * as LanguageModel from "effect/unstable/ai/LanguageModel"
24
+ import type * as Model from "effect/unstable/ai/Model"
19
25
  import * as Config from "effect/Config"
20
26
  import * as KeyValueStore from "effect/unstable/persistence/KeyValueStore"
21
27
  import * as Option from "effect/Option"
22
28
  import { OpenAiClient, OpenAiEmbeddingModel } from "@effect/ai-openai"
23
29
  import { DeviceCodeHandler } from "./index.ts"
24
30
 
25
- const provider = Flag.choice("provider", ["openai", "copilot"]).pipe(
31
+ const version = "0.0.1"
32
+
33
+ type Provider = "openai" | "copilot"
34
+
35
+ const providers: ReadonlyArray<Provider> = ["openai", "copilot"]
36
+
37
+ const withSubagentModel = <E, R>(
38
+ model: Layer.Layer<
39
+ LanguageModel.LanguageModel | Model.ProviderName | Model.ModelName,
40
+ E,
41
+ R
42
+ >,
43
+ ) => Layer.merge(model, Agent.layerSubagentModel(model))
44
+
45
+ const modelLayer = (provider: Provider, model: string, effort: string) =>
46
+ provider === "openai"
47
+ ? withSubagentModel(
48
+ Codex.modelWebSocket(model, { reasoning: { effort: effort as any } }),
49
+ ).pipe(Layer.provide(Codex.layerClient))
50
+ : withSubagentModel(Copilot.model(model, { reasoning: { effort } })).pipe(
51
+ Layer.provide(Copilot.layerClient),
52
+ )
53
+
54
+ const parseModelId = (modelId: string) => {
55
+ const [provider, model, effort, ...rest] = modelId.split("/")
56
+ return provider !== undefined &&
57
+ providers.includes(provider as Provider) &&
58
+ model !== undefined &&
59
+ effort !== undefined &&
60
+ rest.length === 0
61
+ ? Option.some({ provider: provider as Provider, model, effort })
62
+ : Option.none()
63
+ }
64
+
65
+ const ModelId = Schema.String.pipe(
66
+ Schema.check(
67
+ Schema.makeFilter(
68
+ (modelId: string) =>
69
+ Option.isSome(parseModelId(modelId)) ||
70
+ `Invalid model "${modelId}", expected <provider>/<model>/<effort>`,
71
+ ),
72
+ ),
73
+ )
74
+
75
+ const provider = Flag.Literals("provider", providers).pipe(
26
76
  Flag.withAlias("p"),
27
77
  Flag.withFallbackPrompt(
28
- Prompt.select({
78
+ Prompt.Select({
29
79
  message: "Select a provider",
30
80
  choices: [
31
81
  {
@@ -42,12 +92,12 @@ const provider = Flag.choice("provider", ["openai", "copilot"]).pipe(
42
92
  ),
43
93
  )
44
94
 
45
- const model = Flag.string("model").pipe(
95
+ const model = Flag.String("model").pipe(
46
96
  Flag.withAlias("m"),
47
97
  Flag.withFallbackPrompt(
48
- Prompt.text({
98
+ Prompt.String({
49
99
  message: "Enter a model",
50
- default: "gpt-5.4/medium",
100
+ default: "gpt-6-astra/medium",
51
101
  validate(value) {
52
102
  const parts = value.split("/")
53
103
  if (parts.length !== 2) {
@@ -59,7 +109,7 @@ const model = Flag.string("model").pipe(
59
109
  ),
60
110
  )
61
111
 
62
- const semantic = Flag.directory("search").pipe(
112
+ const semantic = Flag.Directory("search").pipe(
63
113
  Flag.withDescription(
64
114
  "Directory for semantic search data (uses OPENAI_API_KEY env var)",
65
115
  ),
@@ -67,7 +117,7 @@ const semantic = Flag.directory("search").pipe(
67
117
  Flag.optional,
68
118
  )
69
119
 
70
- const prompt = Flag.string("prompt").pipe(
120
+ const prompt = Flag.String("prompt").pipe(
71
121
  Flag.withDescription("Pass a prompt in non-interactive mode"),
72
122
  Flag.optional,
73
123
  )
@@ -76,9 +126,9 @@ const Kvs = Layer.unwrap(
76
126
  Effect.gen(function* () {
77
127
  const path = yield* Path.Path
78
128
 
79
- const configHome = yield* Config.nonEmptyString("XDG_CONFIG_HOME").pipe(
129
+ const configHome = yield* Config.NonEmptyString("XDG_CONFIG_HOME").pipe(
80
130
  Config.orElse(() =>
81
- Config.nonEmptyString("HOME").pipe(
131
+ Config.NonEmptyString("HOME").pipe(
82
132
  Config.map((home) => path.join(home, ".config")),
83
133
  ),
84
134
  ),
@@ -90,7 +140,7 @@ const Kvs = Layer.unwrap(
90
140
  const Search = (directory: string) =>
91
141
  Layer.unwrap(
92
142
  Effect.gen(function* () {
93
- const apiKey = yield* Config.redacted("OPENAI_API_KEY").pipe(
143
+ const apiKey = yield* Config.Redacted("OPENAI_API_KEY").pipe(
94
144
  Config.option,
95
145
  )
96
146
 
@@ -123,6 +173,42 @@ const Search = (directory: string) =>
123
173
  }),
124
174
  ).pipe(Layer.provide([NodeServices.layer, NodeHttpClient.layerUndici]))
125
175
 
176
+ const acpModel = Flag.String("model").pipe(
177
+ Flag.withAlias("m"),
178
+ Flag.withDescription(
179
+ "Default model as <provider>/<model>/<effort>, e.g. openai/gpt-6-astra/medium",
180
+ ),
181
+ Flag.withSchema(ModelId),
182
+ Flag.withDefault("openai/gpt-6-astra/medium"),
183
+ )
184
+
185
+ const acp = Command.make("acp", { model: acpModel }).pipe(
186
+ Command.withDescription(
187
+ "Run as an Agent Client Protocol (ACP) server over stdio",
188
+ ),
189
+ Command.withHandler(({ model }) =>
190
+ Acp.runStdio({
191
+ version,
192
+ defaultModel: model,
193
+ makeAgent: (cwd) =>
194
+ Agent.make.pipe(
195
+ Effect.provide(AgentExecutor.layerLocal({ directory: cwd })),
196
+ ),
197
+ makeModel: (modelId) =>
198
+ Option.map(parseModelId(modelId), ({ provider, model, effort }) =>
199
+ modelLayer(provider, model, effort),
200
+ ),
201
+ }),
202
+ ),
203
+ Command.provide(
204
+ Layer.mergeAll(
205
+ Agent.ConversationMode.layer(true),
206
+ Layer.succeed(Logger.LogToStderr, true),
207
+ DeviceCodeHandler.layerLog,
208
+ ),
209
+ ),
210
+ )
211
+
126
212
  Command.make("clanka", { provider, model, semantic, prompt }).pipe(
127
213
  Command.withHandler(
128
214
  Effect.fnUntraced(function* ({
@@ -133,40 +219,7 @@ Command.make("clanka", { provider, model, semantic, prompt }).pipe(
133
219
  }) {
134
220
  const stdio = yield* Stdio.Stdio
135
221
  const [model, reasoning] = modelRaw.split("/") as [string, string]
136
- const Model =
137
- provider === "openai"
138
- ? Codex.modelWebSocket(model, {
139
- reasoning: {
140
- effort: reasoning as any,
141
- },
142
- }).pipe(
143
- Layer.merge(
144
- Agent.layerSubagentModel(
145
- Codex.modelWebSocket("gpt-5.4-mini", {
146
- reasoning: {
147
- effort: "high",
148
- },
149
- }),
150
- ),
151
- ),
152
- Layer.provide(Codex.layerClient),
153
- )
154
- : Copilot.model(model, {
155
- reasoning: {
156
- effort: reasoning,
157
- },
158
- }).pipe(
159
- Layer.merge(
160
- Agent.layerSubagentModel(
161
- Copilot.model(model, {
162
- reasoning: {
163
- effort: "medium",
164
- },
165
- }),
166
- ),
167
- ),
168
- Layer.provide(Copilot.layerClient),
169
- )
222
+ const Model = modelLayer(provider, model, reasoning)
170
223
 
171
224
  return yield* Effect.gen(function* () {
172
225
  const agent = yield* Agent.Agent
@@ -181,7 +234,7 @@ Command.make("clanka", { provider, model, semantic, prompt }).pipe(
181
234
  }
182
235
 
183
236
  while (true) {
184
- const prompt = yield* Prompt.text({
237
+ const prompt = yield* Prompt.String({
185
238
  message: ">",
186
239
  })
187
240
 
@@ -214,8 +267,9 @@ Command.make("clanka", { provider, model, semantic, prompt }).pipe(
214
267
  Command.provide(({ prompt }) =>
215
268
  Agent.ConversationMode.layer(Option.isNone(prompt)),
216
269
  ),
270
+ Command.withSubcommands([acp]),
217
271
  Command.run({
218
- version: "0.0.1",
272
+ version,
219
273
  }),
220
274
  Effect.provide([
221
275
  NodeServices.layer,
package/src/index.ts CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @since 1.0.0
3
+ */
4
+ export * as Acp from "./Acp.ts"
5
+
1
6
  /**
2
7
  * @since 1.0.0
3
8
  */