@llm4ts/core 2.45.0 → 2.47.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/Connector.d.ts +27 -2
  2. package/dist/Connector.d.ts.map +1 -1
  3. package/dist/Connector.js +13 -4
  4. package/dist/Connector.js.map +1 -1
  5. package/dist/ConnectorConfig.d.ts +8 -0
  6. package/dist/ConnectorConfig.d.ts.map +1 -1
  7. package/dist/ConnectorConfig.js +9 -1
  8. package/dist/ConnectorConfig.js.map +1 -1
  9. package/dist/ConnectorRegistry.d.ts +2 -0
  10. package/dist/ConnectorRegistry.d.ts.map +1 -1
  11. package/dist/ConnectorRegistry.js +1 -0
  12. package/dist/ConnectorRegistry.js.map +1 -1
  13. package/dist/Models.d.ts +15 -0
  14. package/dist/Models.d.ts.map +1 -1
  15. package/dist/Models.js +15 -1
  16. package/dist/Models.js.map +1 -1
  17. package/dist/providers/AntigravityConnector.d.ts.map +1 -1
  18. package/dist/providers/AntigravityConnector.js +3 -1
  19. package/dist/providers/AntigravityConnector.js.map +1 -1
  20. package/dist/providers/ClaudeCliConnector.d.ts +17 -1
  21. package/dist/providers/ClaudeCliConnector.d.ts.map +1 -1
  22. package/dist/providers/ClaudeCliConnector.js +45 -4
  23. package/dist/providers/ClaudeCliConnector.js.map +1 -1
  24. package/dist/providers/CliSupport.d.ts +5 -0
  25. package/dist/providers/CliSupport.d.ts.map +1 -1
  26. package/dist/providers/CliSupport.js +5 -0
  27. package/dist/providers/CliSupport.js.map +1 -1
  28. package/dist/providers/CodexConnector.d.ts +7 -1
  29. package/dist/providers/CodexConnector.d.ts.map +1 -1
  30. package/dist/providers/CodexConnector.js +14 -1
  31. package/dist/providers/CodexConnector.js.map +1 -1
  32. package/dist/providers/CopilotConnector.d.ts.map +1 -1
  33. package/dist/providers/CopilotConnector.js +3 -0
  34. package/dist/providers/CopilotConnector.js.map +1 -1
  35. package/dist/providers/CursorConnector.d.ts +6 -1
  36. package/dist/providers/CursorConnector.d.ts.map +1 -1
  37. package/dist/providers/CursorConnector.js +13 -2
  38. package/dist/providers/CursorConnector.js.map +1 -1
  39. package/dist/providers/GeminiCliProvider.d.ts +20 -0
  40. package/dist/providers/GeminiCliProvider.d.ts.map +1 -1
  41. package/dist/providers/GeminiCliProvider.js +64 -3
  42. package/dist/providers/GeminiCliProvider.js.map +1 -1
  43. package/dist/providers/GrokCliConnector.d.ts.map +1 -1
  44. package/dist/providers/GrokCliConnector.js +1 -0
  45. package/dist/providers/GrokCliConnector.js.map +1 -1
  46. package/dist/providers/OpenCodeCliConnector.d.ts.map +1 -1
  47. package/dist/providers/OpenCodeCliConnector.js +2 -0
  48. package/dist/providers/OpenCodeCliConnector.js.map +1 -1
  49. package/dist/providers/PiConnector.d.ts +10 -1
  50. package/dist/providers/PiConnector.d.ts.map +1 -1
  51. package/dist/providers/PiConnector.js +31 -2
  52. package/dist/providers/PiConnector.js.map +1 -1
  53. package/package.json +1 -1
  54. package/src/Connector.ts +43 -9
  55. package/src/ConnectorConfig.ts +9 -1
  56. package/src/ConnectorRegistry.ts +3 -0
  57. package/src/Models.ts +18 -0
  58. package/src/providers/AntigravityConnector.ts +3 -1
  59. package/src/providers/ClaudeCliConnector.ts +52 -4
  60. package/src/providers/CliSupport.ts +7 -0
  61. package/src/providers/CodexConnector.ts +17 -2
  62. package/src/providers/CopilotConnector.ts +3 -0
  63. package/src/providers/CursorConnector.ts +15 -2
  64. package/src/providers/GeminiCliProvider.ts +76 -2
  65. package/src/providers/GrokCliConnector.ts +1 -0
  66. package/src/providers/OpenCodeCliConnector.ts +2 -0
  67. package/src/providers/PiConnector.ts +33 -2
@@ -129,6 +129,10 @@ export class GeminiCliExecutionContext extends Schema.Class<GeminiCliExecutionCo
129
129
  /** The seat's own environment (a roster `env`), under llm4ts's own keys. */
130
130
  envVars: Schema.Record(Schema.String, Schema.String).pipe(
131
131
  Schema.withConstructorDefault(Effect.succeed<Readonly<Record<string, string>>>({}))
132
+ ),
133
+ /** Globs under `cwd` the policy denies every tool (`CliConnectorConfig.deniedPaths`). */
134
+ deniedPaths: Schema.Array(Schema.String).pipe(
135
+ Schema.withConstructorDefault(Effect.succeed(emptyDirectories))
132
136
  )
133
137
  }) {}
134
138
 
@@ -141,6 +145,7 @@ export const geminiCliExecutionContextFrom = (
141
145
  readOnly: config.readOnly,
142
146
  isolated: config.isolated,
143
147
  envVars: config.envVars,
148
+ deniedPaths: config.deniedPaths ?? emptyDirectories,
144
149
  ...(config.sandbox === undefined
145
150
  ? {}
146
151
  : {
@@ -202,6 +207,66 @@ export const geminiHarnessPolicyToml = [
202
207
  ""
203
208
  ].join("\n")
204
209
 
210
+ const regexEscape = (text: string): string => text.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")
211
+
212
+ /** A TOML basic string: backslashes and quotes escaped. */
213
+ const tomlString = (text: string): string =>
214
+ `"${text.replace(/\\/gu, "\\\\").replace(/"/gu, '\\"')}"`
215
+
216
+ /**
217
+ * The pattern that catches `glob` (a directory glob such as `.llm4ts/**`)
218
+ * in a tool call's arguments: the name as a path segment at the start of
219
+ * a relative path (after a quote, a space or `=` in the JSON) or
220
+ * right under `cwd` as an absolute one. A seat whose working directory is
221
+ * a shard worktree under `.llm4ts/port/shards/` therefore keeps its own
222
+ * tree: its paths pass through `.llm4ts` deeper down, under another root.
223
+ * Separators match either way, as the JSON doubles a Windows backslash.
224
+ * It is a match over the argument text: a path reached through `..`, a
225
+ * variable or a glob slips past it, which is why the grade is `partial`.
226
+ */
227
+ export const geminiDeniedPathPattern = (glob: string, cwd: string | undefined): string => {
228
+ const separator = "[\\\\/]+"
229
+ const name = regexEscape(glob.replace(/\/\*\*$/u, ""))
230
+ // A path starts after a quote, whitespace, `=` or a shell operator, and
231
+ // may be spelled `./name`. The working directory as it appears in an
232
+ // argument is anchored the same way, so `/repo` never matches inside
233
+ // `/other/repo/`.
234
+ const boundary = `["'\\s=;&|(\`$]`
235
+ const dot = `(?:\\.${separator})?`
236
+ const under =
237
+ cwd === undefined
238
+ ? ""
239
+ : `(?:${/^[\\/]/u.test(cwd) ? separator : ""}${cwd
240
+ .split(/[\\/]+/u)
241
+ .filter((segment) => segment.length > 0)
242
+ .map(regexEscape)
243
+ .join(separator)}${separator})?`
244
+ return `${boundary}${dot}${under}${name}(?=${separator}|["'\\s;&|)]|$)`
245
+ }
246
+
247
+ /**
248
+ * The harness policy with one admin-tier deny per denied path on top: any
249
+ * tool call — the shell included — whose arguments name the run's own
250
+ * state is refused with a message that says whose it is.
251
+ */
252
+ export const geminiHarnessPolicyFor = (context: GeminiCliExecutionContext): string =>
253
+ [
254
+ geminiHarnessPolicyToml,
255
+ ...context.deniedPaths.map((glob) =>
256
+ [
257
+ "[[rule]]",
258
+ 'toolName = "*"',
259
+ `argsPattern = ${tomlString(geminiDeniedPathPattern(glob, context.cwd))}`,
260
+ 'decision = "deny"',
261
+ "priority = 999",
262
+ `denyMessage = ${tomlString(
263
+ `${glob} is the run state of llm4ts, the tool running you: traces, plans and transcripts that are not part of your task. Do not read, change or delete it.`
264
+ )}`,
265
+ ""
266
+ ].join("\n")
267
+ )
268
+ ].join("\n")
269
+
205
270
  /** Whether `gemini --help` lists `--admin-policy`, the flag the harness policy needs. */
206
271
  export const geminiSupportsAdminPolicy = (help: string): boolean => help.includes("--admin-policy")
207
272
 
@@ -760,9 +825,13 @@ export const makeGeminiCliProvider = (
760
825
  executor: GeminiCliExecutorShape,
761
826
  executionContext: GeminiCliExecutionContext = GeminiCliExecutionContext.make({})
762
827
  ): CliConnectorShape => {
828
+ // `gemini --version` proves the install, not the login: the CLI has no auth
829
+ // status subcommand, and its account file records the last login rather than
830
+ // whether that token still works, so the login stays Unknown. `llm4ts doctor`
831
+ // reports the environment routes (API key, Vertex, project) separately.
763
832
  const healthy = HealthStatus.make({
764
833
  availability: "Healthy",
765
- authStatus: "Valid"
834
+ authStatus: "Unknown"
766
835
  })
767
836
  const unhealthy = HealthStatus.make({
768
837
  availability: "Unhealthy",
@@ -958,7 +1027,12 @@ export const makeGeminiCliProvider = (
958
1027
  capabilities: ConnectorCapabilities.make({
959
1028
  interactiveSessions: true,
960
1029
  readOnlyEnforcement: "enforced",
961
- isolatedHeadless: "partial"
1030
+ isolatedHeadless: "partial",
1031
+ // An admin-tier policy rule over every tool (`geminiHarnessPolicyFor`)
1032
+ // on a CLI that takes `--admin-policy` (older CLIs run without it),
1033
+ // matched over the argument text: a path spelled another way (`..`,
1034
+ // a variable, a glob) slips past, so the grade is partial.
1035
+ pathDeny: "partial"
962
1036
  })
963
1037
  })
964
1038
 
@@ -112,6 +112,7 @@ export const makeGrokCliConnector = (
112
112
  buildInteractiveArgv: (_context) => ["grok", ...extraArgs],
113
113
  complete,
114
114
  completeStream,
115
+ // No auth probe: grok offers `login` but no status subcommand; Unknown.
115
116
  versionProbe: { executor, binary: "grok", cwd }
116
117
  })
117
118
  }
@@ -145,6 +145,8 @@ export const makeOpenCodeCliConnector = (
145
145
  buildInteractiveArgv: (_context) => ["opencode"],
146
146
  complete,
147
147
  completeStream,
148
+ // No auth probe: `opencode auth list` lists stored credentials by
149
+ // provider, not whether the provider this seat runs has any; Unknown.
148
150
  versionProbe: { executor, binary: "opencode", cwd }
149
151
  })
150
152
  }
@@ -1,6 +1,6 @@
1
1
  import * as Effect from "effect/Effect"
2
2
  import * as Stream from "effect/Stream"
3
- import { makeCliConnector, type CliConnectorShape } from "../Connector.ts"
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
6
  import { ConnectorCapabilities, ConnectorIds, LlmChunk, TokenUsage } from "../Models.ts"
@@ -16,6 +16,7 @@ import {
16
16
  jsonStringField,
17
17
  optionalModelArgs,
18
18
  parseJsonLine,
19
+ parseJsonOutput,
19
20
  sortedFlagArgs,
20
21
  toolEventChunk,
21
22
  toolResultChunk,
@@ -25,6 +26,31 @@ import {
25
26
  statusChunk
26
27
  } from "./CliSupport.ts"
27
28
 
29
+ /**
30
+ * `pi auth check --model <model> --no-refresh --json` says whether pi holds
31
+ * credentials for the provider that model resolves to:
32
+ * `{"status":"ready"}` or `{"status":"not_ready","reason":...}`. pi needs a
33
+ * provider or model to answer, so a seat with no model configured has no
34
+ * probe. `--no-refresh` keeps an expired OAuth token where it is: the check
35
+ * stays offline and reports it as not ready.
36
+ */
37
+ export const piAuthProbe = (model: string): CliAuthProbe => ({
38
+ args: ["auth", "check", "--model", model, "--no-refresh", "--json"],
39
+ interpret: (result) => {
40
+ const json = parseJsonOutput(result.stdout)
41
+ switch (jsonStringField(json, "status")) {
42
+ case "ready":
43
+ return "Valid"
44
+ case "not_ready":
45
+ return jsonStringField(json, "reason") === "credentials_not_configured"
46
+ ? "Missing"
47
+ : "Invalid"
48
+ default:
49
+ return "Unknown"
50
+ }
51
+ }
52
+ })
53
+
28
54
  /**
29
55
  * The first pi whose `--tools` allowlist can be kept free of MCP tools: from
30
56
  * 1.0.4 `--tools read` keeps every MCP tool the user configured in
@@ -284,6 +310,11 @@ export const makePiConnector = (
284
310
  buildInteractiveArgv: (_context) => ["pi", ...extraArgs],
285
311
  complete,
286
312
  completeStream,
287
- versionProbe: { executor, binary: "pi", cwd }
313
+ versionProbe: {
314
+ executor,
315
+ binary: "pi",
316
+ cwd,
317
+ ...(config.model === undefined ? {} : { authProbe: piAuthProbe(config.model) })
318
+ }
288
319
  })
289
320
  }