@llm4ts/runner 3.2.6 → 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.
- package/README.md +1 -1
- package/dist/AgentTree.d.ts +2 -3
- package/dist/AgentTree.d.ts.map +1 -1
- package/dist/AgentTree.js +8 -20
- package/dist/AgentTree.js.map +1 -1
- package/dist/AgentTreeSurface.d.ts +1 -1
- package/dist/AgentTreeSurface.d.ts.map +1 -1
- package/dist/AgentTreeSurface.js +2 -5
- package/dist/AgentTreeSurface.js.map +1 -1
- package/dist/Cli.d.ts.map +1 -1
- package/dist/Cli.js +2 -1
- package/dist/Cli.js.map +1 -1
- package/dist/Connectors.d.ts +33 -2
- package/dist/Connectors.d.ts.map +1 -1
- package/dist/Connectors.js +85 -89
- package/dist/Connectors.js.map +1 -1
- package/dist/Doctor.d.ts.map +1 -1
- package/dist/Doctor.js +8 -11
- package/dist/Doctor.js.map +1 -1
- package/dist/EventPresentation.d.ts +35 -0
- package/dist/EventPresentation.d.ts.map +1 -0
- package/dist/EventPresentation.js +133 -0
- package/dist/EventPresentation.js.map +1 -0
- package/dist/ExecutorRoster.d.ts.map +1 -1
- package/dist/ExecutorRoster.js +4 -6
- package/dist/ExecutorRoster.js.map +1 -1
- package/dist/FlowArgs.d.ts +1 -1
- package/dist/FlowArgs.d.ts.map +1 -1
- package/dist/FlowArgs.js +4 -2
- package/dist/FlowArgs.js.map +1 -1
- package/dist/FlowRunner.d.ts +3 -1
- package/dist/FlowRunner.d.ts.map +1 -1
- package/dist/FlowRunner.js +31 -38
- package/dist/FlowRunner.js.map +1 -1
- package/dist/NodeGeminiCliExecutor.d.ts.map +1 -1
- package/dist/NodeGeminiCliExecutor.js +6 -4
- package/dist/NodeGeminiCliExecutor.js.map +1 -1
- package/dist/NodeGeminiModelBridge.d.ts.map +1 -1
- package/dist/NodeGeminiModelBridge.js +3 -1
- package/dist/NodeGeminiModelBridge.js.map +1 -1
- package/dist/Otel.d.ts.map +1 -1
- package/dist/Otel.js +4 -5
- package/dist/Otel.js.map +1 -1
- package/dist/Packs.d.ts.map +1 -1
- package/dist/Packs.js +2 -1
- package/dist/Packs.js.map +1 -1
- package/dist/PiModels.d.ts.map +1 -1
- package/dist/PiModels.js +6 -9
- package/dist/PiModels.js.map +1 -1
- package/dist/Profile.d.ts +4 -0
- package/dist/Profile.d.ts.map +1 -1
- package/dist/Profile.js +45 -75
- package/dist/Profile.js.map +1 -1
- package/dist/RunReaders.d.ts +1 -0
- package/dist/RunReaders.d.ts.map +1 -1
- package/dist/RunReaders.js +4 -51
- package/dist/RunReaders.js.map +1 -1
- package/dist/Terminal.d.ts +14 -11
- package/dist/Terminal.d.ts.map +1 -1
- package/dist/Terminal.js +25 -78
- package/dist/Terminal.js.map +1 -1
- package/dist/Tools.d.ts.map +1 -1
- package/dist/Tools.js +2 -1
- package/dist/Tools.js.map +1 -1
- package/dist/Transcripts.d.ts +1 -1
- package/dist/Transcripts.d.ts.map +1 -1
- package/dist/Transcripts.js +3 -2
- package/dist/Transcripts.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/AgentTree.ts +16 -25
- package/src/AgentTreeSurface.ts +6 -11
- package/src/Cli.ts +2 -1
- package/src/Connectors.ts +141 -107
- package/src/Doctor.ts +10 -12
- package/src/EventPresentation.ts +168 -0
- package/src/ExecutorRoster.ts +4 -6
- package/src/FlowArgs.ts +7 -2
- package/src/FlowRunner.ts +41 -55
- package/src/NodeGeminiCliExecutor.ts +7 -4
- package/src/NodeGeminiModelBridge.ts +4 -1
- package/src/Otel.ts +4 -6
- package/src/Packs.ts +2 -1
- package/src/PiModels.ts +6 -10
- package/src/Profile.ts +64 -81
- package/src/RunReaders.ts +4 -53
- package/src/Terminal.ts +105 -162
- package/src/Tools.ts +2 -1
- package/src/Transcripts.ts +3 -2
- package/src/index.ts +3 -0
package/src/AgentTree.ts
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
+
import * as Duration from "effect/Duration"
|
|
1
2
|
import type { TokenUsage } from "@llm4ts/core/Models"
|
|
2
3
|
import * as Option from "effect/Option"
|
|
3
4
|
import * as Schema from "effect/Schema"
|
|
4
5
|
import { toolCategory } from "@llm4ts/flow/Activity"
|
|
5
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
FlowEvent,
|
|
8
|
+
cloneName,
|
|
9
|
+
laneClosed,
|
|
10
|
+
laneOpened,
|
|
11
|
+
type LaneKind
|
|
12
|
+
} from "@llm4ts/flow/FlowEvents"
|
|
6
13
|
import type { TraceLine } from "@llm4ts/flow/FlowRecorder"
|
|
7
14
|
import { estimateCostUsd } from "@llm4ts/flow/PriceList"
|
|
15
|
+
import { settingValue } from "@llm4ts/flow/Settings"
|
|
8
16
|
import { formatCount } from "./Terminal.ts"
|
|
9
17
|
|
|
10
18
|
/**
|
|
@@ -26,8 +34,7 @@ export type TreeInput =
|
|
|
26
34
|
|
|
27
35
|
export type LaneStatus = "running" | "done" | "failed"
|
|
28
36
|
|
|
29
|
-
|
|
30
|
-
export type LaneKind = "story" | "attendee" | "specialist"
|
|
37
|
+
export type { LaneKind }
|
|
31
38
|
|
|
32
39
|
export interface TreeLane {
|
|
33
40
|
readonly id: string
|
|
@@ -236,16 +243,8 @@ export interface TreeOptions {
|
|
|
236
243
|
export const defaultIdleAfterMs = 120_000
|
|
237
244
|
|
|
238
245
|
/** `LLM4TS_IDLE_AFTER`: `90s`, `5m`, `1h` (a bare number is seconds); two minutes otherwise. */
|
|
239
|
-
export const idleAfterFrom = (
|
|
240
|
-
|
|
241
|
-
): number => {
|
|
242
|
-
const match = /^(\d+)\s*(s|m|h)?$/u.exec(environment.LLM4TS_IDLE_AFTER?.trim() ?? "")
|
|
243
|
-
if (match === null) {
|
|
244
|
-
return defaultIdleAfterMs
|
|
245
|
-
}
|
|
246
|
-
const unit = match[2] === "h" ? 3_600_000 : match[2] === "m" ? 60_000 : 1_000
|
|
247
|
-
return Number(match[1]) * unit
|
|
248
|
-
}
|
|
246
|
+
export const idleAfterFrom = (environment: Readonly<Record<string, string | undefined>>): number =>
|
|
247
|
+
Duration.toMillis(settingValue("LLM4TS_IDLE_AFTER", environment))
|
|
249
248
|
|
|
250
249
|
export const emptyTree = (options: TreeOptions = {}): TreeState => ({
|
|
251
250
|
title: options.title ?? "flow",
|
|
@@ -279,8 +278,6 @@ const logged = (state: TreeState, entry: TreeLogEntry): TreeState => ({
|
|
|
279
278
|
log: [...state.log, entry]
|
|
280
279
|
})
|
|
281
280
|
|
|
282
|
-
/** The stage that opens a lane: a story's, a roundtable attendee's or a taskforce specialist's (names may hold spaces). */
|
|
283
|
-
const laneStage = /^(story|attendee|specialist) (.+)$/u
|
|
284
281
|
const expandedTools = 20
|
|
285
282
|
|
|
286
283
|
const updateLane = (
|
|
@@ -424,15 +421,9 @@ const reduceEvent = (state: TreeState, at: number, event: FlowEvent): TreeState
|
|
|
424
421
|
}
|
|
425
422
|
switch (event._tag) {
|
|
426
423
|
case "StageStarted": {
|
|
427
|
-
const opened =
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
const kind: LaneKind =
|
|
431
|
-
opened?.[1] === "attendee"
|
|
432
|
-
? "attendee"
|
|
433
|
-
: opened?.[1] === "specialist"
|
|
434
|
-
? "specialist"
|
|
435
|
-
: "story"
|
|
424
|
+
const opened = laneOpened(event)
|
|
425
|
+
if (opened !== undefined) {
|
|
426
|
+
const { id: story, kind } = opened
|
|
436
427
|
const fresh: TreeLane = {
|
|
437
428
|
id: story,
|
|
438
429
|
kind,
|
|
@@ -477,7 +468,7 @@ const reduceEvent = (state: TreeState, at: number, event: FlowEvent): TreeState
|
|
|
477
468
|
}
|
|
478
469
|
case "StageCompleted":
|
|
479
470
|
case "StageFailed": {
|
|
480
|
-
if (lane !== undefined && event
|
|
471
|
+
if (lane !== undefined && laneClosed(event)?.kind === lane.kind) {
|
|
481
472
|
const status: LaneStatus = event._tag === "StageFailed" ? "failed" : "done"
|
|
482
473
|
const closed = updateLane(current, lane.id, (open) => ({
|
|
483
474
|
...open,
|
package/src/AgentTreeSurface.ts
CHANGED
|
@@ -5,7 +5,7 @@ import * as Ref from "effect/Ref"
|
|
|
5
5
|
import type * as Scope from "effect/Scope"
|
|
6
6
|
import * as Semaphore from "effect/Semaphore"
|
|
7
7
|
import * as Stream from "effect/Stream"
|
|
8
|
-
import {
|
|
8
|
+
import type { FlowEventHub } from "@llm4ts/flow/FlowEvents"
|
|
9
9
|
import {
|
|
10
10
|
emptyTree,
|
|
11
11
|
initialView,
|
|
@@ -108,17 +108,12 @@ export const makeAgentTreeHost = Effect.fn("@llm4ts/runner/AgentTreeSurface.make
|
|
|
108
108
|
const state = yield* Ref.make(emptyTree(idleAfterMs === undefined ? {} : { idleAfterMs }))
|
|
109
109
|
const view = yield* Ref.make<TreeView>(initialView)
|
|
110
110
|
const active = yield* Ref.make(true)
|
|
111
|
-
const consumed = yield* Ref.make(0)
|
|
112
111
|
const lock = yield* Semaphore.make(1)
|
|
113
112
|
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
Ref.update(state, (current) => reduceTree(current, { _tag: "Event", at, event }))
|
|
119
|
-
).pipe(Effect.andThen(Ref.update(consumed, (count) => count + 1)))
|
|
120
|
-
),
|
|
121
|
-
Effect.forkScoped
|
|
113
|
+
const consumer = yield* hub.consume("agent tree", (event) =>
|
|
114
|
+
Effect.flatMap(Clock.currentTimeMillis, (at) =>
|
|
115
|
+
Ref.update(state, (current) => reduceTree(current, { _tag: "Event", at, event }))
|
|
116
|
+
)
|
|
122
117
|
)
|
|
123
118
|
|
|
124
119
|
const render = (rows: number | undefined): Effect.Effect<ReadonlyArray<string>> =>
|
|
@@ -210,7 +205,7 @@ export const makeAgentTreeHost = Effect.fn("@llm4ts/runner/AgentTreeSurface.make
|
|
|
210
205
|
|
|
211
206
|
return {
|
|
212
207
|
surface,
|
|
213
|
-
close: Effect.andThen(
|
|
208
|
+
close: Effect.andThen(consumer.caughtUp(), leave(true))
|
|
214
209
|
}
|
|
215
210
|
})
|
|
216
211
|
|
package/src/Cli.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { FlowLlmError, type FlowError } from "@llm4ts/flow/FlowError"
|
|
|
3
3
|
import { AssistantMessage } from "@llm4ts/flow/FlowEvents"
|
|
4
4
|
import { collect } from "@llm4ts/core/Streaming"
|
|
5
5
|
import { CliConnectorConfig } from "@llm4ts/core/ConnectorConfig"
|
|
6
|
+
import { settingValue } from "@llm4ts/flow/Settings"
|
|
6
7
|
import { coderFromEnvironment } from "./Connectors.ts"
|
|
7
8
|
import { parseFlowArgs, readFlowPrompt, resolveFlowRepo, type ScriptUsage } from "./FlowArgs.ts"
|
|
8
9
|
import { runNode } from "./FlowRunner.ts"
|
|
@@ -33,7 +34,7 @@ export const makeCliProgram = (
|
|
|
33
34
|
workspace,
|
|
34
35
|
userPrompt: prompt,
|
|
35
36
|
coder,
|
|
36
|
-
verbosity: parseVerbosity(environment
|
|
37
|
+
verbosity: parseVerbosity(settingValue("LLM4TS_VERBOSITY", environment)),
|
|
37
38
|
runId: timestamp.toString()
|
|
38
39
|
},
|
|
39
40
|
(context) =>
|
package/src/Connectors.ts
CHANGED
|
@@ -4,10 +4,22 @@ import * as Redacted from "effect/Redacted"
|
|
|
4
4
|
import {
|
|
5
5
|
ApiConnectorConfig,
|
|
6
6
|
CliConnectorConfig,
|
|
7
|
-
type ConnectorConfig
|
|
7
|
+
type ConnectorConfig,
|
|
8
|
+
type ConnectorConfigOf
|
|
8
9
|
} from "@llm4ts/core/ConnectorConfig"
|
|
9
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
ConnectorIds,
|
|
12
|
+
apiProviderConnector,
|
|
13
|
+
coderConnector,
|
|
14
|
+
coderNames,
|
|
15
|
+
connectorDefaultBaseUrl,
|
|
16
|
+
connectorRow,
|
|
17
|
+
connectorTable,
|
|
18
|
+
offeredApiProviderNames,
|
|
19
|
+
type ConnectorName
|
|
20
|
+
} from "@llm4ts/core/Models"
|
|
10
21
|
import { runStateDeniedPaths } from "@llm4ts/flow/RunState"
|
|
22
|
+
import { settingText, settingValue } from "@llm4ts/flow/Settings"
|
|
11
23
|
import { ScriptUsage } from "./FlowArgs.ts"
|
|
12
24
|
|
|
13
25
|
export const claude = CliConnectorConfig.make({
|
|
@@ -53,12 +65,12 @@ export const opencode = CliConnectorConfig.make({
|
|
|
53
65
|
|
|
54
66
|
export const lmStudio = ApiConnectorConfig.make({
|
|
55
67
|
connectorId: ConnectorIds.LmStudio,
|
|
56
|
-
baseUrl:
|
|
68
|
+
baseUrl: connectorTable.LmStudio.baseUrl
|
|
57
69
|
})
|
|
58
70
|
|
|
59
71
|
export const mlxLm = ApiConnectorConfig.make({
|
|
60
72
|
connectorId: ConnectorIds.MlxLm,
|
|
61
|
-
baseUrl:
|
|
73
|
+
baseUrl: connectorTable.MlxLm.baseUrl
|
|
62
74
|
})
|
|
63
75
|
|
|
64
76
|
export const openAI = ApiConnectorConfig.make({
|
|
@@ -82,6 +94,26 @@ export const mock = ApiConnectorConfig.make({
|
|
|
82
94
|
model: "mock"
|
|
83
95
|
})
|
|
84
96
|
|
|
97
|
+
// A connector without a preset here does not compile.
|
|
98
|
+
const presets: { readonly [K in ConnectorName]: ConnectorConfigOf<K> } = {
|
|
99
|
+
OpenAI: openAI,
|
|
100
|
+
Anthropic: anthropic,
|
|
101
|
+
GeminiApi: geminiApi,
|
|
102
|
+
LmStudio: lmStudio,
|
|
103
|
+
Ollama: ollama,
|
|
104
|
+
MlxLm: mlxLm,
|
|
105
|
+
ClaudeCli: claude,
|
|
106
|
+
GeminiCli: gemini,
|
|
107
|
+
OpenCode: opencode,
|
|
108
|
+
Codex: codex,
|
|
109
|
+
Copilot: CliConnectorConfig.make({ connectorId: ConnectorIds.Copilot }),
|
|
110
|
+
Pi: pi,
|
|
111
|
+
AntigravityCli: antigravity,
|
|
112
|
+
Grok: grok,
|
|
113
|
+
Cursor: cursor,
|
|
114
|
+
Mock: mock
|
|
115
|
+
}
|
|
116
|
+
|
|
85
117
|
export function withModel(config: CliConnectorConfig, model: string): CliConnectorConfig
|
|
86
118
|
export function withModel(config: ApiConnectorConfig, model: string): ApiConnectorConfig
|
|
87
119
|
export function withModel(
|
|
@@ -134,16 +166,12 @@ const environmentApiKey = (
|
|
|
134
166
|
config: ApiConnectorConfig,
|
|
135
167
|
environment: Readonly<Record<string, string | undefined>>
|
|
136
168
|
): string | undefined => {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return environment.GEMINI_API_KEY ?? environment.GOOGLE_API_KEY
|
|
144
|
-
default:
|
|
145
|
-
return undefined
|
|
146
|
-
}
|
|
169
|
+
const row = connectorRow(config.connectorId)
|
|
170
|
+
return row?.kind === "Api"
|
|
171
|
+
? row.credentialVariables
|
|
172
|
+
.map((variable) => environment[variable])
|
|
173
|
+
.find((value) => value !== undefined)
|
|
174
|
+
: undefined
|
|
147
175
|
}
|
|
148
176
|
|
|
149
177
|
export const enrichApiConnector = (
|
|
@@ -163,9 +191,6 @@ export const enrichApiConnector = (
|
|
|
163
191
|
})
|
|
164
192
|
}
|
|
165
193
|
|
|
166
|
-
const switchedOn = (value: string | undefined): boolean =>
|
|
167
|
-
value !== undefined && ["1", "true", "on", "yes"].includes(value.trim().toLowerCase())
|
|
168
|
-
|
|
169
194
|
export const prepareConnector = (
|
|
170
195
|
config: ConnectorConfig,
|
|
171
196
|
workDir: string,
|
|
@@ -179,7 +204,7 @@ export const prepareConnector = (
|
|
|
179
204
|
workingDir: workDir,
|
|
180
205
|
// `LLM4TS_ISOLATED=1` asks every CLI seat of the run to leave the
|
|
181
206
|
// repository's own harness material unloaded (ADR 0029).
|
|
182
|
-
...(
|
|
207
|
+
...(settingValue("LLM4TS_ISOLATED", environment) ? { isolated: true } : {}),
|
|
183
208
|
// Every CLI seat keeps out of the run's own state unless the seat
|
|
184
209
|
// (or the run) says otherwise; the connector maps it to a deny
|
|
185
210
|
// rule where its harness has one (`capabilities.pathDeny`).
|
|
@@ -188,110 +213,124 @@ export const prepareConnector = (
|
|
|
188
213
|
|
|
189
214
|
/** The API provider a name stands for (`LLM4TS_PROVIDER`, a roster executor's `harness`). */
|
|
190
215
|
export const apiPresetFor = (name: string): ApiConnectorConfig | undefined => {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
return mock
|
|
194
|
-
case "openai":
|
|
195
|
-
return openAI
|
|
196
|
-
case "anthropic":
|
|
197
|
-
return anthropic
|
|
198
|
-
case "gemini":
|
|
199
|
-
case "gemini-api":
|
|
200
|
-
return geminiApi
|
|
201
|
-
case "lm-studio":
|
|
202
|
-
case "lmstudio":
|
|
203
|
-
return lmStudio
|
|
204
|
-
case "ollama":
|
|
205
|
-
return ollama
|
|
206
|
-
case "mlx-lm":
|
|
207
|
-
case "mlxlm":
|
|
208
|
-
case "mlx":
|
|
209
|
-
return mlxLm
|
|
210
|
-
default:
|
|
211
|
-
return undefined
|
|
212
|
-
}
|
|
216
|
+
const connector = apiProviderConnector(name.trim().toLowerCase())
|
|
217
|
+
return connector === undefined ? undefined : presets[connector]
|
|
213
218
|
}
|
|
214
219
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
environment: Readonly<Record<string, string | undefined>> = process.env
|
|
220
|
+
const apiConnectorNamed = Effect.fnUntraced(function* (
|
|
221
|
+
named: string,
|
|
222
|
+
model: string | undefined
|
|
219
223
|
): Effect.fn.Return<ApiConnectorConfig, ScriptUsage> {
|
|
220
|
-
const provider =
|
|
224
|
+
const provider = named.toLowerCase()
|
|
221
225
|
const preset = apiPresetFor(provider)
|
|
222
226
|
if (preset === undefined) {
|
|
223
227
|
return yield* ScriptUsage.make({
|
|
224
|
-
message:
|
|
225
|
-
`unknown LLM4TS_PROVIDER '${provider}'; expected ` +
|
|
226
|
-
"mock|openai|anthropic|gemini|lm-studio|ollama|mlx-lm"
|
|
228
|
+
message: `unknown LLM4TS_PROVIDER '${provider}'; expected ${offeredApiProviderNames.join("|")}`
|
|
227
229
|
})
|
|
228
230
|
}
|
|
229
|
-
|
|
230
|
-
if (provider !== "mock" && (model === undefined || model.length === 0)) {
|
|
231
|
+
if (provider !== "mock" && model === undefined) {
|
|
231
232
|
return yield* ScriptUsage.make({
|
|
232
233
|
message: `LLM4TS_MODEL is required for provider '${provider}' (e.g. LLM4TS_MODEL=gpt-4o-mini)`
|
|
233
234
|
})
|
|
234
235
|
}
|
|
235
|
-
return model === undefined
|
|
236
|
+
return model === undefined ? preset : withModel(preset, model)
|
|
236
237
|
})
|
|
237
238
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
[gemini, ["gemini"]],
|
|
247
|
-
[pi, ["pi"]],
|
|
248
|
-
[antigravity, ["agy", "antigravity"]],
|
|
249
|
-
[grok, ["grok"]],
|
|
250
|
-
[cursor, ["cursor"]],
|
|
251
|
-
[opencode, ["opencode"]]
|
|
252
|
-
]
|
|
253
|
-
|
|
254
|
-
const coders = new Map<string, CliConnectorConfig>(
|
|
255
|
-
coderAliases.flatMap(([preset, aliases]) =>
|
|
256
|
-
[...aliases, preset.connectorId.value].map((name) => [name, preset] as const)
|
|
239
|
+
export const apiConnectorFromEnvironment = Effect.fn(
|
|
240
|
+
"@llm4ts/runner/Connectors.apiConnectorFromEnvironment"
|
|
241
|
+
)(function* (
|
|
242
|
+
environment: Readonly<Record<string, string | undefined>> = process.env
|
|
243
|
+
): Effect.fn.Return<ApiConnectorConfig, ScriptUsage> {
|
|
244
|
+
return yield* apiConnectorNamed(
|
|
245
|
+
settingText("LLM4TS_PROVIDER", environment) ?? "mock",
|
|
246
|
+
settingValue("LLM4TS_MODEL", environment)
|
|
257
247
|
)
|
|
258
|
-
)
|
|
248
|
+
})
|
|
259
249
|
|
|
260
|
-
export const coderIds: ReadonlyArray<string> = Object.freeze([...
|
|
250
|
+
export const coderIds: ReadonlyArray<string> = Object.freeze([...coderNames].sort())
|
|
261
251
|
|
|
262
|
-
export const coderFor = (name: string): CliConnectorConfig | undefined =>
|
|
263
|
-
|
|
252
|
+
export const coderFor = (name: string): CliConnectorConfig | undefined => {
|
|
253
|
+
const connector = coderConnector(name.trim().toLowerCase())
|
|
254
|
+
return connector === undefined ? undefined : presets[connector]
|
|
255
|
+
}
|
|
264
256
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
): CliConnectorConfig => {
|
|
271
|
-
const requested = (env.LLM4TS_CODER ?? "").trim()
|
|
272
|
-
return (requested.length === 0 ? claude : coderFor(requested)) ?? claude
|
|
257
|
+
export interface SeatDefaults {
|
|
258
|
+
/** The coder when `LLM4TS_CODER` is unset; claude when omitted. */
|
|
259
|
+
readonly coder?: CliConnectorConfig
|
|
260
|
+
/** The reasoner when `LLM4TS_REASONER` is unset; the coder seat when omitted. */
|
|
261
|
+
readonly reasoner?: CliConnectorConfig
|
|
273
262
|
}
|
|
274
263
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
264
|
+
export interface Seats {
|
|
265
|
+
readonly coder: CliConnectorConfig
|
|
266
|
+
readonly reasoning: CliConnectorConfig
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
type Environment = Readonly<Record<string, string | undefined>>
|
|
270
|
+
|
|
271
|
+
const namedSeat = (
|
|
272
|
+
variable: "LLM4TS_CODER" | "LLM4TS_REASONER",
|
|
273
|
+
environment: Environment
|
|
274
|
+
): Effect.Effect<CliConnectorConfig | undefined, ScriptUsage> => {
|
|
275
|
+
const requested = settingText(variable, environment)
|
|
276
|
+
if (requested === undefined) {
|
|
277
|
+
return Effect.succeed(undefined)
|
|
278
|
+
}
|
|
279
|
+
const preset = coderFor(requested)
|
|
280
|
+
return preset === undefined
|
|
281
|
+
? Effect.fail(
|
|
282
|
+
ScriptUsage.make({
|
|
283
|
+
message: `unknown ${variable} '${requested}'; expected ${coderIds.join("|")}`
|
|
290
284
|
})
|
|
291
|
-
|
|
285
|
+
)
|
|
286
|
+
: Effect.succeed(preset)
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* The coder and reasoning seats of a flow that runs both, from `LLM4TS_CODER`
|
|
291
|
+
* and `LLM4TS_REASONER`. An unknown name is a usage error naming the accepted
|
|
292
|
+
* names, never another agent. A flow with one seat reads that one alone
|
|
293
|
+
* (`coderFromEnvironment`, `reasonerFromEnvironment`).
|
|
294
|
+
*/
|
|
295
|
+
export const seatsFromEnvironment = Effect.fn("@llm4ts/runner/Connectors.seatsFromEnvironment")(
|
|
296
|
+
function* (
|
|
297
|
+
defaults: SeatDefaults = {},
|
|
298
|
+
environment: Environment = process.env
|
|
299
|
+
): Effect.fn.Return<Seats, ScriptUsage> {
|
|
300
|
+
const coder = (yield* namedSeat("LLM4TS_CODER", environment)) ?? defaults.coder ?? claude
|
|
301
|
+
const reasoner = yield* namedSeat("LLM4TS_REASONER", environment)
|
|
302
|
+
return { coder, reasoning: reasoner ?? defaults.reasoner ?? coder }
|
|
292
303
|
}
|
|
293
304
|
)
|
|
294
305
|
|
|
306
|
+
/**
|
|
307
|
+
* An unknown name runs claude, with nothing said about it.
|
|
308
|
+
*
|
|
309
|
+
* @deprecated Use `seatsFromEnvironment`, which refuses an unknown name. Removed in 4.0.0.
|
|
310
|
+
*/
|
|
311
|
+
export const coderFromEnv = (env: Environment = process.env): CliConnectorConfig => {
|
|
312
|
+
const requested = settingValue("LLM4TS_CODER", env)
|
|
313
|
+
return (requested === undefined ? claude : coderFor(requested)) ?? claude
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* `LLM4TS_CODER` alone, for a flow that runs no separate reasoning seat. A
|
|
318
|
+
* flow answers only for the seats it uses: a bad `LLM4TS_REASONER` is not
|
|
319
|
+
* this flow's to refuse.
|
|
320
|
+
*/
|
|
321
|
+
export const coderFromEnvironment = (
|
|
322
|
+
environment: Environment = process.env,
|
|
323
|
+
fallback: CliConnectorConfig = claude
|
|
324
|
+
): Effect.Effect<CliConnectorConfig, ScriptUsage> =>
|
|
325
|
+
Effect.map(namedSeat("LLM4TS_CODER", environment), (coder) => coder ?? fallback)
|
|
326
|
+
|
|
327
|
+
/** `LLM4TS_REASONER` alone, for a flow that edits nothing. */
|
|
328
|
+
export const reasonerFromEnvironment = (
|
|
329
|
+
environment: Environment = process.env,
|
|
330
|
+
fallback: CliConnectorConfig = claude
|
|
331
|
+
): Effect.Effect<CliConnectorConfig, ScriptUsage> =>
|
|
332
|
+
Effect.map(namedSeat("LLM4TS_REASONER", environment), (reasoner) => reasoner ?? fallback)
|
|
333
|
+
|
|
295
334
|
/**
|
|
296
335
|
* The judgment seat from the environment (ADR 0017): `LLM4TS_JUDGMENT_PROVIDER`
|
|
297
336
|
* names an API provider the way `LLM4TS_PROVIDER` does, `LLM4TS_JUDGMENT_MODEL`
|
|
@@ -302,13 +341,8 @@ export const judgmentConnectorFromEnvironment = Effect.fn(
|
|
|
302
341
|
)(function* (
|
|
303
342
|
environment: Readonly<Record<string, string | undefined>> = process.env
|
|
304
343
|
): Effect.fn.Return<ApiConnectorConfig | undefined, ScriptUsage> {
|
|
305
|
-
const provider = environment
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
return yield* apiConnectorFromEnvironment({
|
|
310
|
-
...environment,
|
|
311
|
-
LLM4TS_PROVIDER: provider,
|
|
312
|
-
LLM4TS_MODEL: environment.LLM4TS_JUDGMENT_MODEL
|
|
313
|
-
})
|
|
344
|
+
const provider = settingText("LLM4TS_JUDGMENT_PROVIDER", environment)
|
|
345
|
+
return provider === undefined
|
|
346
|
+
? undefined
|
|
347
|
+
: yield* apiConnectorNamed(provider, settingValue("LLM4TS_JUDGMENT_MODEL", environment))
|
|
314
348
|
})
|
package/src/Doctor.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect"
|
|
2
2
|
import type { ConnectorKind, HealthStatus } from "@llm4ts/core/Models"
|
|
3
|
-
import { ConnectorIds } from "@llm4ts/core/Models"
|
|
3
|
+
import { ConnectorIds, credentialVariables } from "@llm4ts/core/Models"
|
|
4
4
|
import type { ConnectorRegistryShape } from "@llm4ts/core/ConnectorRegistry"
|
|
5
5
|
import { nodePreflightReport, type NodePreflightReport } from "@llm4ts/flow/NodePreflight"
|
|
6
|
+
import { settingText, settingValue } from "@llm4ts/flow/Settings"
|
|
6
7
|
import { nodeFlowRunnerDependencies } from "./FlowRunner.ts"
|
|
7
8
|
import { nodePlainFileStore } from "./NodePlainFileStore.ts"
|
|
8
9
|
import { nodeProcessExecutor } from "./NodeProcessExecutor.ts"
|
|
@@ -15,13 +16,6 @@ import {
|
|
|
15
16
|
import { systemAdminPolicies, systemPolicyProblem } from "./NodeGeminiModelBridge.ts"
|
|
16
17
|
import { otelConfig, otelDoctorLine, probeOtelEndpoint, type OtelConfig } from "./Otel.ts"
|
|
17
18
|
|
|
18
|
-
const credentialKeys = [
|
|
19
|
-
"OPENAI_API_KEY",
|
|
20
|
-
"ANTHROPIC_API_KEY",
|
|
21
|
-
"GEMINI_API_KEY",
|
|
22
|
-
"GOOGLE_API_KEY"
|
|
23
|
-
] as const
|
|
24
|
-
|
|
25
19
|
/**
|
|
26
20
|
* How far a connector's health was verified: `"auth"` means the CLI's own
|
|
27
21
|
* login check answered (`authStatus` is its word); `"version"` means only
|
|
@@ -133,7 +127,7 @@ const wrap = (text: string, width: number, indent: string): ReadonlyArray<string
|
|
|
133
127
|
}
|
|
134
128
|
|
|
135
129
|
const selectedCoder = (environment: Readonly<Record<string, string | undefined>>): string =>
|
|
136
|
-
|
|
130
|
+
settingText("LLM4TS_CODER", environment) ?? "claude (default)"
|
|
137
131
|
|
|
138
132
|
// Re-exported so `@llm4ts/runner/Doctor` stays the one place callers look
|
|
139
133
|
// for pi-config questions, while the parsing itself has no dependency on
|
|
@@ -161,7 +155,7 @@ export const geminiBridgePrerequisites = (
|
|
|
161
155
|
environment: Readonly<Record<string, string | undefined>>,
|
|
162
156
|
modelsJson: string | undefined
|
|
163
157
|
): PrerequisiteReport | undefined => {
|
|
164
|
-
if (!
|
|
158
|
+
if (!settingValue("LLM4TS_GEMINI_BRIDGE", environment)) {
|
|
165
159
|
return undefined
|
|
166
160
|
}
|
|
167
161
|
const port = bridgePort(environment)
|
|
@@ -254,7 +248,11 @@ export const geminiModelModePrerequisites = (
|
|
|
254
248
|
systemPolicies: ReadonlyArray<string>
|
|
255
249
|
): PrerequisiteReport | undefined => {
|
|
256
250
|
const mode = geminiBridgeMode(environment)
|
|
257
|
-
if (
|
|
251
|
+
if (
|
|
252
|
+
!settingValue("LLM4TS_GEMINI_BRIDGE", environment) ||
|
|
253
|
+
mode._tag !== "Mode" ||
|
|
254
|
+
mode.mode !== "model"
|
|
255
|
+
) {
|
|
258
256
|
return undefined
|
|
259
257
|
}
|
|
260
258
|
const detail = [
|
|
@@ -324,7 +322,7 @@ export const collectDoctorReport = (
|
|
|
324
322
|
const lines: Array<string> = []
|
|
325
323
|
lines.push("")
|
|
326
324
|
lines.push("credentials:")
|
|
327
|
-
for (const key of
|
|
325
|
+
for (const key of credentialVariables) {
|
|
328
326
|
const value = environment[key]
|
|
329
327
|
lines.push(` ${value === undefined || value.length === 0 ? "✖" : "✔"} ${key}`)
|
|
330
328
|
}
|