@llm4ts/core 3.3.0 → 3.4.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 (67) hide show
  1. package/dist/ConnectorConfig.d.ts +3 -1
  2. package/dist/ConnectorConfig.d.ts.map +1 -1
  3. package/dist/ConnectorConfig.js +4 -17
  4. package/dist/ConnectorConfig.js.map +1 -1
  5. package/dist/Models.d.ts +177 -20
  6. package/dist/Models.d.ts.map +1 -1
  7. package/dist/Models.js +151 -98
  8. package/dist/Models.js.map +1 -1
  9. package/dist/eval/Eval.d.ts +4 -1
  10. package/dist/eval/Eval.d.ts.map +1 -1
  11. package/dist/eval/Eval.js.map +1 -1
  12. package/dist/eval/Evaluator.d.ts +2 -6
  13. package/dist/eval/Evaluator.d.ts.map +1 -1
  14. package/dist/eval/Evaluator.js.map +1 -1
  15. package/dist/providers/AntigravityConnector.d.ts.map +1 -1
  16. package/dist/providers/AntigravityConnector.js +5 -4
  17. package/dist/providers/AntigravityConnector.js.map +1 -1
  18. package/dist/providers/ClaudeAgentSession.d.ts.map +1 -1
  19. package/dist/providers/ClaudeAgentSession.js +3 -2
  20. package/dist/providers/ClaudeAgentSession.js.map +1 -1
  21. package/dist/providers/ClaudeCliConnector.d.ts.map +1 -1
  22. package/dist/providers/ClaudeCliConnector.js +7 -6
  23. package/dist/providers/ClaudeCliConnector.js.map +1 -1
  24. package/dist/providers/CodexConnector.d.ts.map +1 -1
  25. package/dist/providers/CodexConnector.js +8 -7
  26. package/dist/providers/CodexConnector.js.map +1 -1
  27. package/dist/providers/ConnectorFactories.d.ts.map +1 -1
  28. package/dist/providers/ConnectorFactories.js +26 -20
  29. package/dist/providers/ConnectorFactories.js.map +1 -1
  30. package/dist/providers/CopilotConnector.d.ts.map +1 -1
  31. package/dist/providers/CopilotConnector.js +5 -4
  32. package/dist/providers/CopilotConnector.js.map +1 -1
  33. package/dist/providers/CursorConnector.d.ts.map +1 -1
  34. package/dist/providers/CursorConnector.js +6 -5
  35. package/dist/providers/CursorConnector.js.map +1 -1
  36. package/dist/providers/GeminiAcpSession.d.ts.map +1 -1
  37. package/dist/providers/GeminiAcpSession.js +3 -1
  38. package/dist/providers/GeminiAcpSession.js.map +1 -1
  39. package/dist/providers/GeminiCliProvider.d.ts.map +1 -1
  40. package/dist/providers/GeminiCliProvider.js +4 -3
  41. package/dist/providers/GeminiCliProvider.js.map +1 -1
  42. package/dist/providers/GrokCliConnector.d.ts.map +1 -1
  43. package/dist/providers/GrokCliConnector.js +11 -5
  44. package/dist/providers/GrokCliConnector.js.map +1 -1
  45. package/dist/providers/OpenCodeCliConnector.d.ts.map +1 -1
  46. package/dist/providers/OpenCodeCliConnector.js +6 -5
  47. package/dist/providers/OpenCodeCliConnector.js.map +1 -1
  48. package/dist/providers/PiConnector.d.ts.map +1 -1
  49. package/dist/providers/PiConnector.js +8 -7
  50. package/dist/providers/PiConnector.js.map +1 -1
  51. package/package.json +1 -1
  52. package/src/ConnectorConfig.ts +18 -17
  53. package/src/Models.ts +245 -100
  54. package/src/eval/Eval.ts +4 -1
  55. package/src/eval/Evaluator.ts +2 -5
  56. package/src/providers/AntigravityConnector.ts +12 -4
  57. package/src/providers/ClaudeAgentSession.ts +4 -2
  58. package/src/providers/ClaudeCliConnector.ts +9 -6
  59. package/src/providers/CodexConnector.ts +10 -7
  60. package/src/providers/ConnectorFactories.ts +53 -51
  61. package/src/providers/CopilotConnector.ts +6 -4
  62. package/src/providers/CursorConnector.ts +7 -5
  63. package/src/providers/GeminiAcpSession.ts +4 -1
  64. package/src/providers/GeminiCliProvider.ts +6 -3
  65. package/src/providers/GrokCliConnector.ts +12 -5
  66. package/src/providers/OpenCodeCliConnector.ts +7 -5
  67. package/src/providers/PiConnector.ts +15 -7
@@ -11,7 +11,8 @@ import {
11
11
  ConnectorIds,
12
12
  LlmChunk,
13
13
  TokenUsage,
14
- type JsonSchema
14
+ type JsonSchema,
15
+ cliExecutable
15
16
  } from "../Models.ts"
16
17
  import type { ProcessExecutorShape } from "../ProcessExecutor.ts"
17
18
  import { collect } from "../Streaming.ts"
@@ -37,6 +38,8 @@ import {
37
38
  jsonObjectEntries
38
39
  } from "./CliSupport.ts"
39
40
 
41
+ const executable = cliExecutable("ClaudeCli")
42
+
40
43
  /**
41
44
  * `claude auth status --json` reads the credential store and answers
42
45
  * `{"loggedIn": true|false, ...}` without a request to Anthropic. A claude
@@ -243,7 +246,7 @@ export const makeClaudeCliConnector = (
243
246
  prompt: string
244
247
  ) {
245
248
  const result = yield* executor.runWithStdin(
246
- ["claude", "--print", ...extraArgs],
249
+ [executable, "--print", ...extraArgs],
247
250
  cwd,
248
251
  config.envVars,
249
252
  prompt
@@ -269,7 +272,7 @@ export const makeClaudeCliConnector = (
269
272
  ([model, said]) =>
270
273
  executor
271
274
  .runStreamingWithStdin(
272
- ["claude", "--print", "--output-format", "stream-json", "--verbose", ...extraArgs],
275
+ [executable, "--print", "--output-format", "stream-json", "--verbose", ...extraArgs],
273
276
  cwd,
274
277
  config.envVars,
275
278
  prompt
@@ -341,11 +344,11 @@ export const makeClaudeCliConnector = (
341
344
  // another way are not), so the grade is partial.
342
345
  pathDeny: "partial"
343
346
  }),
344
- buildArgv: (prompt, _context) => ["claude", "--print", ...extraArgs, prompt],
345
- buildInteractiveArgv: (_context) => ["claude", ...extraArgs],
347
+ buildArgv: (prompt, _context) => [executable, "--print", ...extraArgs, prompt],
348
+ buildInteractiveArgv: (_context) => [executable, ...extraArgs],
346
349
  complete,
347
350
  completeStream,
348
- versionProbe: { executor, binary: "claude", cwd, authProbe: claudeAuthProbe }
351
+ versionProbe: { executor, binary: executable, cwd, authProbe: claudeAuthProbe }
349
352
  })
350
353
 
351
354
  const executeStructuredWithUsage = <A, E, RD, RE>(
@@ -10,7 +10,8 @@ import {
10
10
  ConnectorIds,
11
11
  LlmChunk,
12
12
  TokenUsage,
13
- type JsonSchema
13
+ type JsonSchema,
14
+ cliExecutable
14
15
  } from "../Models.ts"
15
16
  import type { ProcessExecutorShape } from "../ProcessExecutor.ts"
16
17
  import { collect } from "../Streaming.ts"
@@ -33,6 +34,8 @@ import {
33
34
  type JsonValue
34
35
  } from "./CliSupport.ts"
35
36
 
37
+ const executable = cliExecutable("Codex")
38
+
36
39
  export const codexExtraArgs = (config: CliConnectorConfig): ReadonlyArray<string> => {
37
40
  const effectiveFlags = config.readOnly
38
41
  ? {
@@ -251,7 +254,7 @@ export const makeCodexConnector = (
251
254
  prompt: string
252
255
  ) {
253
256
  const result = yield* executor.runWithStdin(
254
- ["codex", "exec", ...extraArgs],
257
+ [executable, "exec", ...extraArgs],
255
258
  cwd,
256
259
  config.envVars,
257
260
  prompt
@@ -265,7 +268,7 @@ export const makeCodexConnector = (
265
268
  })
266
269
 
267
270
  const completeStream = (prompt: string) =>
268
- rawStream(["codex", "exec", "--json", ...extraArgs], prompt).pipe(
271
+ rawStream([executable, "exec", "--json", ...extraArgs], prompt).pipe(
269
272
  Stream.mapEffect(failCodexChunk)
270
273
  )
271
274
 
@@ -279,11 +282,11 @@ export const makeCodexConnector = (
279
282
  effort: "mapped",
280
283
  isolatedHeadless: "partial"
281
284
  }),
282
- buildArgv: (prompt, _context) => ["codex", "exec", ...extraArgs, prompt],
283
- buildInteractiveArgv: (_context) => ["codex", ...extraArgs],
285
+ buildArgv: (prompt, _context) => [executable, "exec", ...extraArgs, prompt],
286
+ buildInteractiveArgv: (_context) => [executable, ...extraArgs],
284
287
  complete,
285
288
  completeStream,
286
- versionProbe: { executor, binary: "codex", cwd, authProbe: codexAuthProbe }
289
+ versionProbe: { executor, binary: executable, cwd, authProbe: codexAuthProbe }
287
290
  })
288
291
 
289
292
  const executeStructuredWithUsage = <A, E, RD, RE>(
@@ -308,7 +311,7 @@ export const makeCodexConnector = (
308
311
  jsonText(codexStrictSchema(jsonSchema))
309
312
  )
310
313
  const reply = yield* collect(
311
- rawStream(["codex", "exec", "--json", "--output-schema", path, ...extraArgs], prompt)
314
+ rawStream([executable, "exec", "--json", "--output-schema", path, ...extraArgs], prompt)
312
315
  )
313
316
  if (reply.metadata.codexError !== undefined) {
314
317
  return yield* failClassifiedCliError("codex", "codex error", reply.metadata.codexError)
@@ -1,10 +1,12 @@
1
1
  import * as Effect from "effect/Effect"
2
2
  import * as Layer from "effect/Layer"
3
+ import * as Record from "effect/Record"
3
4
  import {
4
5
  ApiConnectorConfig,
5
6
  CliConnectorConfig,
6
7
  toLlmConfig,
7
- type ConnectorConfig
8
+ type ConnectorConfig,
9
+ type ConnectorConfigOf
8
10
  } from "../ConnectorConfig.ts"
9
11
  import {
10
12
  ConnectorRegistry,
@@ -15,7 +17,14 @@ import {
15
17
  import type { ConnectorShape } from "../Connector.ts"
16
18
  import { ConfigError, type LlmError } from "../Errors.ts"
17
19
  import { HttpClient, type HttpClientShape } from "../HttpClient.ts"
18
- import { ConnectorIds, LlmConfig, type ConnectorId, type ConnectorKind } from "../Models.ts"
20
+ import {
21
+ ConnectorIds,
22
+ LlmConfig,
23
+ isCliConnectorName,
24
+ type ConnectorId,
25
+ type ConnectorKind,
26
+ type ConnectorName
27
+ } from "../Models.ts"
19
28
  import { ProcessExecutor, type ProcessExecutorShape } from "../ProcessExecutor.ts"
20
29
  import { TemporaryFiles, type TemporaryFilesShape } from "../TemporaryFiles.ts"
21
30
  import { makeAnthropicProvider } from "./AnthropicProvider.ts"
@@ -76,58 +85,51 @@ const cliFactory = (
76
85
  : Effect.fail(expectedConfig("Cli", id))
77
86
  })
78
87
 
88
+ // A connector without an entry here does not compile. The registry lists
89
+ // them in this order.
90
+ const builders: {
91
+ readonly [K in ConnectorName]: (
92
+ config: ConnectorConfigOf<K>,
93
+ dependencies: ConnectorFactoryDependencies
94
+ ) => ConnectorShape
95
+ } = {
96
+ OpenAI: (config, { http }) => makeOpenAIProvider(toLlmConfig(config), http),
97
+ Anthropic: (config, { http }) => makeAnthropicProvider(toLlmConfig(config), http),
98
+ GeminiApi: (config, { http }) => makeGeminiApiProvider(toLlmConfig(config), http),
99
+ LmStudio: (config, { http }) => makeLmStudioProvider(toLlmConfig(config), http),
100
+ Ollama: (config, { http }) => makeOllamaProvider(toLlmConfig(config), http),
101
+ MlxLm: (config, { http }) => makeMlxLmProvider(toLlmConfig(config), http),
102
+ ClaudeCli: (config, { process }) => makeClaudeCliConnector(config, process),
103
+ OpenCode: (config, { process }) => makeOpenCodeCliConnector(config, process),
104
+ GeminiCli: (config, { geminiCli }) =>
105
+ makeGeminiCliProvider(
106
+ LlmConfig.make({
107
+ provider: "GeminiCli",
108
+ model: config.model ?? "gemini-2.5-flash"
109
+ }),
110
+ geminiCli,
111
+ geminiCliExecutionContextFrom(config)
112
+ ),
113
+ Codex: (config, { process, temporaryFiles }) =>
114
+ makeCodexConnector(config, process, temporaryFiles),
115
+ Copilot: (config, { process }) => makeCopilotConnector(config, process),
116
+ Pi: (config, { process }) => makePiConnector(config, process),
117
+ AntigravityCli: (config, { process }) => makeAntigravityConnector(config, process),
118
+ Grok: (config, { process }) => makeGrokCliConnector(config, process),
119
+ Cursor: (config, { process }) => makeCursorConnector(config, process),
120
+ Mock: (config) => makeMockProvider(toLlmConfig(config))
121
+ }
122
+
79
123
  export const createConnectorRegistry = (
80
124
  dependencies: ConnectorFactoryDependencies
81
125
  ): ConnectorRegistryShape =>
82
- makeConnectorRegistry([
83
- apiFactory(ConnectorIds.OpenAI, (config) =>
84
- makeOpenAIProvider(toLlmConfig(config), dependencies.http)
85
- ),
86
- apiFactory(ConnectorIds.Anthropic, (config) =>
87
- makeAnthropicProvider(toLlmConfig(config), dependencies.http)
88
- ),
89
- apiFactory(ConnectorIds.GeminiApi, (config) =>
90
- makeGeminiApiProvider(toLlmConfig(config), dependencies.http)
91
- ),
92
- apiFactory(ConnectorIds.LmStudio, (config) =>
93
- makeLmStudioProvider(toLlmConfig(config), dependencies.http)
94
- ),
95
- apiFactory(ConnectorIds.Ollama, (config) =>
96
- makeOllamaProvider(toLlmConfig(config), dependencies.http)
97
- ),
98
- apiFactory(ConnectorIds.MlxLm, (config) =>
99
- makeMlxLmProvider(toLlmConfig(config), dependencies.http)
100
- ),
101
- cliFactory(ConnectorIds.ClaudeCli, (config) =>
102
- makeClaudeCliConnector(config, dependencies.process)
103
- ),
104
- cliFactory(ConnectorIds.OpenCode, (config) =>
105
- makeOpenCodeCliConnector(config, dependencies.process)
106
- ),
107
- cliFactory(ConnectorIds.GeminiCli, (config) =>
108
- makeGeminiCliProvider(
109
- LlmConfig.make({
110
- provider: "GeminiCli",
111
- model: config.model ?? "gemini-2.5-flash"
112
- }),
113
- dependencies.geminiCli,
114
- geminiCliExecutionContextFrom(config)
115
- )
116
- ),
117
- cliFactory(ConnectorIds.Codex, (config) =>
118
- makeCodexConnector(config, dependencies.process, dependencies.temporaryFiles)
119
- ),
120
- cliFactory(ConnectorIds.Copilot, (config) =>
121
- makeCopilotConnector(config, dependencies.process)
122
- ),
123
- cliFactory(ConnectorIds.Pi, (config) => makePiConnector(config, dependencies.process)),
124
- cliFactory(ConnectorIds.AntigravityCli, (config) =>
125
- makeAntigravityConnector(config, dependencies.process)
126
- ),
127
- cliFactory(ConnectorIds.Grok, (config) => makeGrokCliConnector(config, dependencies.process)),
128
- cliFactory(ConnectorIds.Cursor, (config) => makeCursorConnector(config, dependencies.process)),
129
- apiFactory(ConnectorIds.Mock, (config) => makeMockProvider(toLlmConfig(config)))
130
- ])
126
+ makeConnectorRegistry(
127
+ Record.keys(builders).map((name) =>
128
+ isCliConnectorName(name)
129
+ ? cliFactory(ConnectorIds[name], (config) => builders[name](config, dependencies))
130
+ : apiFactory(ConnectorIds[name], (config) => builders[name](config, dependencies))
131
+ )
132
+ )
131
133
 
132
134
  export const ConnectorRegistryLive = Layer.effect(
133
135
  ConnectorRegistry,
@@ -2,12 +2,14 @@ import * as Effect from "effect/Effect"
2
2
  import * as Stream from "effect/Stream"
3
3
  import { makeCliConnector, type CliConnectorShape } from "../Connector.ts"
4
4
  import type { CliConnectorConfig } from "../ConnectorConfig.ts"
5
- import { ConnectorCapabilities, ConnectorIds, LlmChunk } from "../Models.ts"
5
+ import { ConnectorCapabilities, ConnectorIds, LlmChunk, cliExecutable } from "../Models.ts"
6
6
  import type { ProcessExecutorShape } from "../ProcessExecutor.ts"
7
7
  import { failClassifiedCliError } from "./CliSupport.ts"
8
8
 
9
+ const executable = cliExecutable("Copilot")
10
+
9
11
  export const buildCopilotArgv = (prompt: string): ReadonlyArray<string> => [
10
- "gh",
12
+ executable,
11
13
  "copilot",
12
14
  "suggest",
13
15
  "-t",
@@ -46,7 +48,7 @@ export const makeCopilotConnector = (
46
48
  readOnlyEnforcement: "ignored"
47
49
  }),
48
50
  buildArgv: (prompt, _context) => buildCopilotArgv(prompt),
49
- buildInteractiveArgv: (_context) => ["gh", "copilot"],
51
+ buildInteractiveArgv: (_context) => [executable, "copilot"],
50
52
  complete,
51
53
  completeStream: (prompt) =>
52
54
  Stream.fromEffect(complete(prompt)).pipe(
@@ -55,6 +57,6 @@ export const makeCopilotConnector = (
55
57
  // No auth probe: `gh auth status` validates every stored token against
56
58
  // the GitHub API (and `gh auth token` would print the secret), so there
57
59
  // is no offline login check to run; the login stays Unknown.
58
- versionProbe: { executor, binary: "gh", versionArgs: ["copilot", "--version"], cwd: "." }
60
+ versionProbe: { executor, binary: executable, versionArgs: ["copilot", "--version"], cwd: "." }
59
61
  })
60
62
  }
@@ -2,7 +2,7 @@ import * as Effect from "effect/Effect"
2
2
  import * as Stream from "effect/Stream"
3
3
  import { makeCliConnector, type CliAuthProbe, type CliConnectorShape } from "../Connector.ts"
4
4
  import type { CliConnectorConfig } from "../ConnectorConfig.ts"
5
- import { ConnectorCapabilities, ConnectorIds, LlmChunk } from "../Models.ts"
5
+ import { ConnectorCapabilities, ConnectorIds, LlmChunk, cliExecutable } from "../Models.ts"
6
6
  import type { ProcessExecutorShape } from "../ProcessExecutor.ts"
7
7
  import {
8
8
  failClassifiedCliError,
@@ -19,6 +19,8 @@ import {
19
19
  type JsonValue
20
20
  } from "./CliSupport.ts"
21
21
 
22
+ const executable = cliExecutable("Cursor")
23
+
22
24
  /**
23
25
  * `cursor-agent status --format json` (alias `whoami`) answers
24
26
  * `{"isAuthenticated": true|false, ...}` from the stored tokens.
@@ -84,7 +86,7 @@ export const makeCursorConnector = (
84
86
  const cwd = config.workingDir ?? "."
85
87
  const extraArgs = cursorExtraArgs(config)
86
88
  const buildArgv = (prompt: string): ReadonlyArray<string> => [
87
- "cursor-agent",
89
+ executable,
88
90
  ...extraArgs,
89
91
  "-p",
90
92
  prompt
@@ -107,7 +109,7 @@ export const makeCursorConnector = (
107
109
  const completeStream = (prompt: string) =>
108
110
  executor
109
111
  .runStreaming(
110
- ["cursor-agent", "--output-format", "stream-json", ...extraArgs, "-p", prompt],
112
+ [executable, "--output-format", "stream-json", ...extraArgs, "-p", prompt],
111
113
  cwd,
112
114
  config.envVars
113
115
  )
@@ -133,9 +135,9 @@ export const makeCursorConnector = (
133
135
  readOnlyEnforcement: "ignored"
134
136
  }),
135
137
  buildArgv: (prompt, _context) => buildArgv(prompt),
136
- buildInteractiveArgv: (_context) => ["cursor-agent", ...extraArgs],
138
+ buildInteractiveArgv: (_context) => [executable, ...extraArgs],
137
139
  complete,
138
140
  completeStream,
139
- versionProbe: { executor, binary: "cursor-agent", cwd, authProbe: cursorAuthProbe }
141
+ versionProbe: { executor, binary: executable, cwd, authProbe: cursorAuthProbe }
140
142
  })
141
143
  }
@@ -16,6 +16,9 @@ import {
16
16
  type JsonRecord,
17
17
  type JsonValue
18
18
  } from "./CliSupport.ts"
19
+ import { cliExecutable } from "../Models.ts"
20
+
21
+ const executable = cliExecutable("GeminiCli")
19
22
 
20
23
  /**
21
24
  * ACP (Agent Client Protocol, `gemini --experimental-acp`) message shapes
@@ -28,7 +31,7 @@ import {
28
31
  * integration smoke test, not a substitute for that determinism.
29
32
  */
30
33
  export const geminiAcpArgv = (model: string | undefined): ReadonlyArray<string> => [
31
- "gemini",
34
+ executable,
32
35
  "--experimental-acp",
33
36
  ...(model === undefined ? [] : ["-m", model])
34
37
  ]
@@ -22,7 +22,8 @@ import {
22
22
  TokenUsage,
23
23
  type JsonSchema,
24
24
  type LlmConfig,
25
- type Message
25
+ type Message,
26
+ cliExecutable
26
27
  } from "../Models.ts"
27
28
  import { collect } from "../Streaming.ts"
28
29
  import { jsonCandidates, parseFromText, withSchemaHint } from "../StructuredOutput.ts"
@@ -41,6 +42,8 @@ import {
41
42
  type JsonValue
42
43
  } from "./CliSupport.ts"
43
44
 
45
+ const executable = cliExecutable("GeminiCli")
46
+
44
47
  export class GeminiCliLogLine extends Schema.TaggedClass<GeminiCliLogLine>()("LogLine", {
45
48
  line: Schema.String
46
49
  }) {}
@@ -1005,14 +1008,14 @@ export const makeGeminiCliProvider = (
1005
1008
  id: ConnectorIds.GeminiCli,
1006
1009
  interactionSupport: "InteractiveStdin",
1007
1010
  buildArgv: (prompt, context) => [
1008
- "gemini",
1011
+ executable,
1009
1012
  "--yolo",
1010
1013
  ...(context.repoPath.length === 0 ? [] : ["--include-directories", context.repoPath]),
1011
1014
  "-p",
1012
1015
  prompt
1013
1016
  ],
1014
1017
  buildInteractiveArgv: (context) => [
1015
- "gemini",
1018
+ executable,
1016
1019
  "--yolo",
1017
1020
  ...(context.repoPath.length === 0 ? [] : ["--include-directories", context.repoPath])
1018
1021
  ],
@@ -2,7 +2,7 @@ import * as Effect from "effect/Effect"
2
2
  import * as Stream from "effect/Stream"
3
3
  import { makeCliConnector, type CliConnectorShape } from "../Connector.ts"
4
4
  import type { CliConnectorConfig } from "../ConnectorConfig.ts"
5
- import { ConnectorIds, LlmChunk, TokenUsage } from "../Models.ts"
5
+ import { ConnectorIds, LlmChunk, TokenUsage, cliExecutable } from "../Models.ts"
6
6
  import type { ProcessExecutorShape } from "../ProcessExecutor.ts"
7
7
  import {
8
8
  failClassifiedCliError,
@@ -16,6 +16,8 @@ import {
16
16
  usageEventChunk
17
17
  } from "./CliSupport.ts"
18
18
 
19
+ const executable = cliExecutable("Grok")
20
+
19
21
  export const grokCliExtraArgs = (config: CliConnectorConfig): ReadonlyArray<string> => [
20
22
  ...optionalModelArgs(config.model),
21
23
  ...(config.readOnly ? ["--permission-mode", "plan"] : ["--always-approve"]),
@@ -75,7 +77,12 @@ export const makeGrokCliConnector = (
75
77
  ): CliConnectorShape => {
76
78
  const cwd = config.workingDir ?? "."
77
79
  const extraArgs = grokCliExtraArgs(config)
78
- const buildArgv = (prompt: string): ReadonlyArray<string> => ["grok", ...extraArgs, "-p", prompt]
80
+ const buildArgv = (prompt: string): ReadonlyArray<string> => [
81
+ executable,
82
+ ...extraArgs,
83
+ "-p",
84
+ prompt
85
+ ]
79
86
 
80
87
  const complete = Effect.fn("@llm4ts/core/providers/GrokCliConnector.complete")(function* (
81
88
  prompt: string
@@ -91,7 +98,7 @@ export const makeGrokCliConnector = (
91
98
  const completeStream = (prompt: string) =>
92
99
  executor
93
100
  .runStreaming(
94
- ["grok", "--output-format", "streaming-json", ...extraArgs, "-p", prompt],
101
+ [executable, "--output-format", "streaming-json", ...extraArgs, "-p", prompt],
95
102
  cwd,
96
103
  config.envVars
97
104
  )
@@ -109,10 +116,10 @@ export const makeGrokCliConnector = (
109
116
  id: ConnectorIds.Grok,
110
117
  interactionSupport: "ContinuationOnly",
111
118
  buildArgv: (prompt, _context) => buildArgv(prompt),
112
- buildInteractiveArgv: (_context) => ["grok", ...extraArgs],
119
+ buildInteractiveArgv: (_context) => [executable, ...extraArgs],
113
120
  complete,
114
121
  completeStream,
115
122
  // No auth probe: grok offers `login` but no status subcommand; Unknown.
116
- versionProbe: { executor, binary: "grok", cwd }
123
+ versionProbe: { executor, binary: executable, cwd }
117
124
  })
118
125
  }
@@ -2,7 +2,7 @@ import * as Effect from "effect/Effect"
2
2
  import * as Stream from "effect/Stream"
3
3
  import { makeCliConnector, type CliConnectorShape } from "../Connector.ts"
4
4
  import type { CliConnectorConfig } from "../ConnectorConfig.ts"
5
- import { ConnectorIds, LlmChunk, TokenUsage } from "../Models.ts"
5
+ import { ConnectorIds, LlmChunk, TokenUsage, cliExecutable } from "../Models.ts"
6
6
  import type { ProcessExecutorShape } from "../ProcessExecutor.ts"
7
7
  import {
8
8
  failClassifiedCliError,
@@ -17,6 +17,8 @@ import {
17
17
  usageEventChunk
18
18
  } from "./CliSupport.ts"
19
19
 
20
+ const executable = cliExecutable("OpenCode")
21
+
20
22
  export const openCodeCliExtraArgs = (config: CliConnectorConfig): ReadonlyArray<string> => [
21
23
  ...optionalModelArgs(config.model),
22
24
  ...(config.readOnly ? ["--agent", "plan"] : ["--auto"]),
@@ -98,7 +100,7 @@ export const makeOpenCodeCliConnector = (
98
100
  const cwd = config.workingDir ?? "."
99
101
  const extraArgs = openCodeCliExtraArgs(config)
100
102
  const buildArgv = (prompt: string): ReadonlyArray<string> => [
101
- "opencode",
103
+ executable,
102
104
  "run",
103
105
  ...extraArgs,
104
106
  prompt
@@ -122,7 +124,7 @@ export const makeOpenCodeCliConnector = (
122
124
  const completeStream = (prompt: string) =>
123
125
  executor
124
126
  .runStreaming(
125
- ["opencode", "run", "--format", "json", ...extraArgs, prompt],
127
+ [executable, "run", "--format", "json", ...extraArgs, prompt],
126
128
  cwd,
127
129
  config.envVars
128
130
  )
@@ -142,11 +144,11 @@ export const makeOpenCodeCliConnector = (
142
144
  id: ConnectorIds.OpenCode,
143
145
  interactionSupport: "ContinuationOnly",
144
146
  buildArgv: (prompt, _context) => buildArgv(prompt),
145
- buildInteractiveArgv: (_context) => ["opencode"],
147
+ buildInteractiveArgv: (_context) => [executable],
146
148
  complete,
147
149
  completeStream,
148
150
  // No auth probe: `opencode auth list` lists stored credentials by
149
151
  // provider, not whether the provider this seat runs has any; Unknown.
150
- versionProbe: { executor, binary: "opencode", cwd }
152
+ versionProbe: { executor, binary: executable, cwd }
151
153
  })
152
154
  }
@@ -3,7 +3,13 @@ import * as Stream from "effect/Stream"
3
3
  import { makeCliConnector, type CliAuthProbe, type CliConnectorShape } from "../Connector.ts"
4
4
  import type { CliConnectorConfig } from "../ConnectorConfig.ts"
5
5
  import { InvalidRequestError, ProviderError, type LlmError } from "../Errors.ts"
6
- import { ConnectorCapabilities, ConnectorIds, LlmChunk, TokenUsage } from "../Models.ts"
6
+ import {
7
+ ConnectorCapabilities,
8
+ ConnectorIds,
9
+ LlmChunk,
10
+ TokenUsage,
11
+ cliExecutable
12
+ } from "../Models.ts"
7
13
  import type { ProcessExecutorShape } from "../ProcessExecutor.ts"
8
14
  import {
9
15
  atLeastVersion,
@@ -26,6 +32,8 @@ import {
26
32
  statusChunk
27
33
  } from "./CliSupport.ts"
28
34
 
35
+ const executable = cliExecutable("Pi")
36
+
29
37
  /**
30
38
  * `pi auth check --model <model> --no-refresh --json` says whether pi holds
31
39
  * credentials for the provider that model resolves to:
@@ -235,7 +243,7 @@ export const makePiConnector = (
235
243
  const checkFloorOnce: Effect.Effect<void, LlmError> = Effect.suspend(() =>
236
244
  floorChecked
237
245
  ? Effect.void
238
- : Effect.flatMap(executor.run(["pi", "--version"], cwd, config.envVars), (result) => {
246
+ : Effect.flatMap(executor.run([executable, "--version"], cwd, config.envVars), (result) => {
239
247
  const problem = piReadOnlyProblem([...result.stdout, ...result.stderr].join("\n"))
240
248
  return problem === undefined
241
249
  ? Effect.sync(() => {
@@ -250,7 +258,7 @@ export const makePiConnector = (
250
258
  ) {
251
259
  yield* checkFloorOnce
252
260
  const result = yield* executor.runWithStdin(
253
- ["pi", "-p", ...extraArgs],
261
+ [executable, "-p", ...extraArgs],
254
262
  cwd,
255
263
  config.envVars,
256
264
  prompt
@@ -273,7 +281,7 @@ export const makePiConnector = (
273
281
  Stream.drain,
274
282
  Stream.concat(
275
283
  executor.runStreamingWithStdin(
276
- ["pi", "-p", "--mode", "json", ...extraArgs],
284
+ [executable, "-p", "--mode", "json", ...extraArgs],
277
285
  cwd,
278
286
  config.envVars,
279
287
  prompt
@@ -306,13 +314,13 @@ export const makePiConnector = (
306
314
  }),
307
315
  // `--` ends the options (pi >= 0.84.3): a prompt starting with a dash
308
316
  // is a prompt, not a flag. Stdin paths need no separator.
309
- buildArgv: (prompt, _context) => ["pi", "-p", ...extraArgs, "--", prompt],
310
- buildInteractiveArgv: (_context) => ["pi", ...extraArgs],
317
+ buildArgv: (prompt, _context) => [executable, "-p", ...extraArgs, "--", prompt],
318
+ buildInteractiveArgv: (_context) => [executable, ...extraArgs],
311
319
  complete,
312
320
  completeStream,
313
321
  versionProbe: {
314
322
  executor,
315
- binary: "pi",
323
+ binary: executable,
316
324
  cwd,
317
325
  ...(config.model === undefined ? {} : { authProbe: piAuthProbe(config.model) })
318
326
  }