@llm4ts/runner 0.3.0 → 0.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/dist/Doctor.d.ts +22 -0
- package/dist/Doctor.d.ts.map +1 -1
- package/dist/Doctor.js +81 -1
- package/dist/Doctor.js.map +1 -1
- package/package.json +3 -3
- package/src/Doctor.ts +103 -3
package/dist/Doctor.d.ts
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect";
|
|
2
2
|
import type { ConnectorRegistryShape } from "@llm4ts/core/ConnectorRegistry";
|
|
3
|
+
export interface PrerequisiteReport {
|
|
4
|
+
readonly satisfied: boolean;
|
|
5
|
+
readonly summary: string;
|
|
6
|
+
readonly hint?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The Gemini CLI resolves credentials from the environment before it ever
|
|
10
|
+
* looks at a prompt, and a Workspace or enterprise account with no project
|
|
11
|
+
* configured fails during auth setup with "No project found" / "requires
|
|
12
|
+
* setting the GOOGLE_CLOUD_PROJECT … env var" — a message that names nothing
|
|
13
|
+
* about llm4ts and is easy to misread as a flow bug.
|
|
14
|
+
*
|
|
15
|
+
* The CLI accepts several routes; any one of them is enough:
|
|
16
|
+
* - `GEMINI_API_KEY` (AI Studio key),
|
|
17
|
+
* - Vertex AI (`GOOGLE_GENAI_USE_VERTEXAI` plus a project or `GOOGLE_API_KEY`),
|
|
18
|
+
* - `GOOGLE_CLOUD_PROJECT` / `GOOGLE_CLOUD_PROJECT_ID` for Code Assist,
|
|
19
|
+
* - or a personal OAuth login, which needs no environment at all.
|
|
20
|
+
*
|
|
21
|
+
* Personal OAuth cannot be observed from the environment, so an unconfigured
|
|
22
|
+
* environment is reported as a caveat rather than a failure.
|
|
23
|
+
*/
|
|
24
|
+
export declare const geminiPrerequisites: (environment: Readonly<Record<string, string | undefined>>) => PrerequisiteReport;
|
|
3
25
|
export declare const makeDoctorProgram: (registry?: ConnectorRegistryShape, environment?: Readonly<Record<string, string | undefined>>) => Effect.Effect<string>;
|
|
4
26
|
//# sourceMappingURL=Doctor.d.ts.map
|
package/dist/Doctor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Doctor.d.ts","sourceRoot":"","sources":["../src/Doctor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"Doctor.d.ts","sourceRoot":"","sources":["../src/Doctor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAA;AAuB5E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,mBAAmB,GAC9B,aAAa,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,KACxD,kBA0BF,CAAA;AAwBD,eAAO,MAAM,iBAAiB,GAC5B,WAAU,sBAA8D,EACxE,cAAa,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAe,KACtE,MAAM,CAAC,MAAM,CAAC,MAAM,CAwCnB,CAAA"}
|
package/dist/Doctor.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect";
|
|
2
|
+
import { ConnectorIds } from "@llm4ts/core/Models";
|
|
2
3
|
import { nodeFlowRunnerDependencies } from "./FlowRunner.js";
|
|
3
4
|
const credentialKeys = [
|
|
4
5
|
"OPENAI_API_KEY",
|
|
@@ -11,6 +12,70 @@ const statusLine = (id, status) => {
|
|
|
11
12
|
const auth = status.authStatus === "Unknown" ? "" : ` auth: ${status.authStatus.toLowerCase()}`;
|
|
12
13
|
return ` ${mark} ${id.padEnd(14)} ${status.availability.toLowerCase()}${auth}`;
|
|
13
14
|
};
|
|
15
|
+
const isSet = (value) => value !== undefined && value.trim().length > 0;
|
|
16
|
+
const truthy = (value) => isSet(value) && ["1", "true", "yes"].includes(value.trim().toLowerCase());
|
|
17
|
+
/**
|
|
18
|
+
* The Gemini CLI resolves credentials from the environment before it ever
|
|
19
|
+
* looks at a prompt, and a Workspace or enterprise account with no project
|
|
20
|
+
* configured fails during auth setup with "No project found" / "requires
|
|
21
|
+
* setting the GOOGLE_CLOUD_PROJECT … env var" — a message that names nothing
|
|
22
|
+
* about llm4ts and is easy to misread as a flow bug.
|
|
23
|
+
*
|
|
24
|
+
* The CLI accepts several routes; any one of them is enough:
|
|
25
|
+
* - `GEMINI_API_KEY` (AI Studio key),
|
|
26
|
+
* - Vertex AI (`GOOGLE_GENAI_USE_VERTEXAI` plus a project or `GOOGLE_API_KEY`),
|
|
27
|
+
* - `GOOGLE_CLOUD_PROJECT` / `GOOGLE_CLOUD_PROJECT_ID` for Code Assist,
|
|
28
|
+
* - or a personal OAuth login, which needs no environment at all.
|
|
29
|
+
*
|
|
30
|
+
* Personal OAuth cannot be observed from the environment, so an unconfigured
|
|
31
|
+
* environment is reported as a caveat rather than a failure.
|
|
32
|
+
*/
|
|
33
|
+
export const geminiPrerequisites = (environment) => {
|
|
34
|
+
const project = environment.GOOGLE_CLOUD_PROJECT ?? environment.GOOGLE_CLOUD_PROJECT_ID;
|
|
35
|
+
const vertex = truthy(environment.GOOGLE_GENAI_USE_VERTEXAI);
|
|
36
|
+
if (isSet(environment.GEMINI_API_KEY)) {
|
|
37
|
+
return { satisfied: true, summary: "GEMINI_API_KEY is set" };
|
|
38
|
+
}
|
|
39
|
+
if (vertex && (isSet(project) || isSet(environment.GOOGLE_API_KEY))) {
|
|
40
|
+
return {
|
|
41
|
+
satisfied: true,
|
|
42
|
+
summary: isSet(project)
|
|
43
|
+
? `Vertex AI with GOOGLE_CLOUD_PROJECT=${project.trim()}`
|
|
44
|
+
: "Vertex AI with GOOGLE_API_KEY"
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (isSet(project)) {
|
|
48
|
+
return { satisfied: true, summary: `GOOGLE_CLOUD_PROJECT=${project.trim()}` };
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
satisfied: false,
|
|
52
|
+
summary: "no project or API key in the environment",
|
|
53
|
+
hint: "a personal OAuth login still works; a Workspace or enterprise account fails at auth " +
|
|
54
|
+
'setup with "No project found". Export GOOGLE_CLOUD_PROJECT (or GEMINI_API_KEY) in a file ' +
|
|
55
|
+
"every shell reads — a login-only file such as ~/.bashrc is not read by non-interactive " +
|
|
56
|
+
"shells, other shells, or IDE terminals."
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
/** Wraps a hint to the terminal-friendly width the rest of the report uses. */
|
|
60
|
+
const wrap = (text, width, indent) => {
|
|
61
|
+
const out = [];
|
|
62
|
+
let line = "";
|
|
63
|
+
for (const word of text.split(/\s+/).filter((part) => part.length > 0)) {
|
|
64
|
+
const candidate = line.length === 0 ? word : `${line} ${word}`;
|
|
65
|
+
if (candidate.length + indent.length > width && line.length > 0) {
|
|
66
|
+
out.push(`${indent}${line}`);
|
|
67
|
+
line = word;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
line = candidate;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (line.length > 0) {
|
|
74
|
+
out.push(`${indent}${line}`);
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
};
|
|
78
|
+
const selectedCoder = (environment) => environment.LLM4TS_CODER ?? environment.LLM4ZIO_CODER ?? "claude (default)";
|
|
14
79
|
export const makeDoctorProgram = (registry = nodeFlowRunnerDependencies().registry, environment = process.env) => Effect.map(registry.healthCheckAll, (statuses) => {
|
|
15
80
|
const lines = [];
|
|
16
81
|
lines.push("llm4ts doctor");
|
|
@@ -26,8 +91,23 @@ export const makeDoctorProgram = (registry = nodeFlowRunnerDependencies().regist
|
|
|
26
91
|
const value = environment[key];
|
|
27
92
|
lines.push(` ${value === undefined || value.length === 0 ? "✖" : "✔"} ${key}`);
|
|
28
93
|
}
|
|
94
|
+
// Connector prerequisites the environment must carry BEFORE a run: shown
|
|
95
|
+
// when the connector is selected or the machine has its CLI, because that
|
|
96
|
+
// is exactly when a missing one turns into a confusing mid-run failure.
|
|
97
|
+
const coder = selectedCoder(environment);
|
|
98
|
+
const geminiRelevant = coder.startsWith("gemini") ||
|
|
99
|
+
Array.from(statuses.keys()).some((id) => id.value === ConnectorIds.GeminiCli.value || id.value === ConnectorIds.GeminiApi.value);
|
|
100
|
+
if (geminiRelevant) {
|
|
101
|
+
const gemini = geminiPrerequisites(environment);
|
|
102
|
+
lines.push("");
|
|
103
|
+
lines.push("prerequisites:");
|
|
104
|
+
lines.push(` ${gemini.satisfied ? "✔" : "?"} gemini: ${gemini.summary}`);
|
|
105
|
+
if (gemini.hint !== undefined) {
|
|
106
|
+
lines.push(...wrap(gemini.hint, 78, " "));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
29
109
|
lines.push("");
|
|
30
|
-
lines.push(`coder: ${
|
|
110
|
+
lines.push(`coder: ${coder}`);
|
|
31
111
|
return `${lines.join("\n")}\n`;
|
|
32
112
|
});
|
|
33
113
|
//# sourceMappingURL=Doctor.js.map
|
package/dist/Doctor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Doctor.js","sourceRoot":"","sources":["../src/Doctor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"Doctor.js","sourceRoot":"","sources":["../src/Doctor.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llm4ts/runner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Node runner, CLI, terminal, and MCP composition for llm4ts",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"typescript"
|
|
56
56
|
],
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@llm4ts/
|
|
59
|
-
"@llm4ts/
|
|
58
|
+
"@llm4ts/core": "0.4.0",
|
|
59
|
+
"@llm4ts/flow": "0.4.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"effect": "^4.0.0-beta.102"
|
package/src/Doctor.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect"
|
|
2
2
|
import type { HealthStatus } from "@llm4ts/core/Models"
|
|
3
|
+
import { ConnectorIds } from "@llm4ts/core/Models"
|
|
3
4
|
import type { ConnectorRegistryShape } from "@llm4ts/core/ConnectorRegistry"
|
|
4
5
|
import { nodeFlowRunnerDependencies } from "./FlowRunner.ts"
|
|
5
6
|
|
|
@@ -17,6 +18,86 @@ const statusLine = (id: string, status: HealthStatus): string => {
|
|
|
17
18
|
return ` ${mark} ${id.padEnd(14)} ${status.availability.toLowerCase()}${auth}`
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
const isSet = (value: string | undefined): value is string =>
|
|
22
|
+
value !== undefined && value.trim().length > 0
|
|
23
|
+
|
|
24
|
+
const truthy = (value: string | undefined): boolean =>
|
|
25
|
+
isSet(value) && ["1", "true", "yes"].includes(value.trim().toLowerCase())
|
|
26
|
+
|
|
27
|
+
export interface PrerequisiteReport {
|
|
28
|
+
readonly satisfied: boolean
|
|
29
|
+
readonly summary: string
|
|
30
|
+
readonly hint?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The Gemini CLI resolves credentials from the environment before it ever
|
|
35
|
+
* looks at a prompt, and a Workspace or enterprise account with no project
|
|
36
|
+
* configured fails during auth setup with "No project found" / "requires
|
|
37
|
+
* setting the GOOGLE_CLOUD_PROJECT … env var" — a message that names nothing
|
|
38
|
+
* about llm4ts and is easy to misread as a flow bug.
|
|
39
|
+
*
|
|
40
|
+
* The CLI accepts several routes; any one of them is enough:
|
|
41
|
+
* - `GEMINI_API_KEY` (AI Studio key),
|
|
42
|
+
* - Vertex AI (`GOOGLE_GENAI_USE_VERTEXAI` plus a project or `GOOGLE_API_KEY`),
|
|
43
|
+
* - `GOOGLE_CLOUD_PROJECT` / `GOOGLE_CLOUD_PROJECT_ID` for Code Assist,
|
|
44
|
+
* - or a personal OAuth login, which needs no environment at all.
|
|
45
|
+
*
|
|
46
|
+
* Personal OAuth cannot be observed from the environment, so an unconfigured
|
|
47
|
+
* environment is reported as a caveat rather than a failure.
|
|
48
|
+
*/
|
|
49
|
+
export const geminiPrerequisites = (
|
|
50
|
+
environment: Readonly<Record<string, string | undefined>>
|
|
51
|
+
): PrerequisiteReport => {
|
|
52
|
+
const project = environment.GOOGLE_CLOUD_PROJECT ?? environment.GOOGLE_CLOUD_PROJECT_ID
|
|
53
|
+
const vertex = truthy(environment.GOOGLE_GENAI_USE_VERTEXAI)
|
|
54
|
+
if (isSet(environment.GEMINI_API_KEY)) {
|
|
55
|
+
return { satisfied: true, summary: "GEMINI_API_KEY is set" }
|
|
56
|
+
}
|
|
57
|
+
if (vertex && (isSet(project) || isSet(environment.GOOGLE_API_KEY))) {
|
|
58
|
+
return {
|
|
59
|
+
satisfied: true,
|
|
60
|
+
summary: isSet(project)
|
|
61
|
+
? `Vertex AI with GOOGLE_CLOUD_PROJECT=${project.trim()}`
|
|
62
|
+
: "Vertex AI with GOOGLE_API_KEY"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (isSet(project)) {
|
|
66
|
+
return { satisfied: true, summary: `GOOGLE_CLOUD_PROJECT=${project.trim()}` }
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
satisfied: false,
|
|
70
|
+
summary: "no project or API key in the environment",
|
|
71
|
+
hint:
|
|
72
|
+
"a personal OAuth login still works; a Workspace or enterprise account fails at auth " +
|
|
73
|
+
'setup with "No project found". Export GOOGLE_CLOUD_PROJECT (or GEMINI_API_KEY) in a file ' +
|
|
74
|
+
"every shell reads — a login-only file such as ~/.bashrc is not read by non-interactive " +
|
|
75
|
+
"shells, other shells, or IDE terminals."
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Wraps a hint to the terminal-friendly width the rest of the report uses. */
|
|
80
|
+
const wrap = (text: string, width: number, indent: string): ReadonlyArray<string> => {
|
|
81
|
+
const out: Array<string> = []
|
|
82
|
+
let line = ""
|
|
83
|
+
for (const word of text.split(/\s+/).filter((part) => part.length > 0)) {
|
|
84
|
+
const candidate = line.length === 0 ? word : `${line} ${word}`
|
|
85
|
+
if (candidate.length + indent.length > width && line.length > 0) {
|
|
86
|
+
out.push(`${indent}${line}`)
|
|
87
|
+
line = word
|
|
88
|
+
} else {
|
|
89
|
+
line = candidate
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (line.length > 0) {
|
|
93
|
+
out.push(`${indent}${line}`)
|
|
94
|
+
}
|
|
95
|
+
return out
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const selectedCoder = (environment: Readonly<Record<string, string | undefined>>): string =>
|
|
99
|
+
environment.LLM4TS_CODER ?? environment.LLM4ZIO_CODER ?? "claude (default)"
|
|
100
|
+
|
|
20
101
|
export const makeDoctorProgram = (
|
|
21
102
|
registry: ConnectorRegistryShape = nodeFlowRunnerDependencies().registry,
|
|
22
103
|
environment: Readonly<Record<string, string | undefined>> = process.env
|
|
@@ -36,9 +117,28 @@ export const makeDoctorProgram = (
|
|
|
36
117
|
const value = environment[key]
|
|
37
118
|
lines.push(` ${value === undefined || value.length === 0 ? "✖" : "✔"} ${key}`)
|
|
38
119
|
}
|
|
120
|
+
|
|
121
|
+
// Connector prerequisites the environment must carry BEFORE a run: shown
|
|
122
|
+
// when the connector is selected or the machine has its CLI, because that
|
|
123
|
+
// is exactly when a missing one turns into a confusing mid-run failure.
|
|
124
|
+
const coder = selectedCoder(environment)
|
|
125
|
+
const geminiRelevant =
|
|
126
|
+
coder.startsWith("gemini") ||
|
|
127
|
+
Array.from(statuses.keys()).some(
|
|
128
|
+
(id) =>
|
|
129
|
+
id.value === ConnectorIds.GeminiCli.value || id.value === ConnectorIds.GeminiApi.value
|
|
130
|
+
)
|
|
131
|
+
if (geminiRelevant) {
|
|
132
|
+
const gemini = geminiPrerequisites(environment)
|
|
133
|
+
lines.push("")
|
|
134
|
+
lines.push("prerequisites:")
|
|
135
|
+
lines.push(` ${gemini.satisfied ? "✔" : "?"} gemini: ${gemini.summary}`)
|
|
136
|
+
if (gemini.hint !== undefined) {
|
|
137
|
+
lines.push(...wrap(gemini.hint, 78, " "))
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
39
141
|
lines.push("")
|
|
40
|
-
lines.push(
|
|
41
|
-
`coder: ${environment.LLM4TS_CODER ?? environment.LLM4ZIO_CODER ?? "claude (default)"}`
|
|
42
|
-
)
|
|
142
|
+
lines.push(`coder: ${coder}`)
|
|
43
143
|
return `${lines.join("\n")}\n`
|
|
44
144
|
})
|