@2kw/ai 6.3.0-dev.75 → 6.3.0-dev.84
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/commands/agent-run.d.ts +5 -0
- package/dist/commands/agent-run.js +24 -13
- package/dist/commands/ai.js +2 -2
- package/dist/lib/agent-run.d.ts +21 -1
- package/dist/lib/agent-run.js +119 -8
- package/dist/lib/config.d.ts +8 -0
- package/dist/lib/config.js +28 -0
- package/package.json +1 -1
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
export declare function resolveRunInput(arg: string | undefined, inputFile: string | undefined, readStdin?: () => string | undefined): string;
|
|
3
|
+
/**
|
|
4
|
+
* The task text from the argument, then the file, then stdin; `undefined` when none is given. A
|
|
5
|
+
* `--continue` run may have none: a connect pause is continued with an empty input (#807).
|
|
6
|
+
*/
|
|
7
|
+
export declare function readRunInput(arg: string | undefined, inputFile: string | undefined, readStdin?: () => string | undefined): string | undefined;
|
|
3
8
|
export declare function makeAgentRunCommand(): Command;
|
|
4
9
|
export declare function makeAgentDecideCommand(): Command;
|
|
5
10
|
//# sourceMappingURL=agent-run.d.ts.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { getClient, runAction } from "../lib/client.js";
|
|
4
|
-
import { isJsonOutput } from "../lib/config.js";
|
|
4
|
+
import { chatUrlFor, isJsonOutput, resolveConfig } from "../lib/config.js";
|
|
5
5
|
import { BackboneApiError, CliUsageError } from "../lib/errors.js";
|
|
6
6
|
import { withSpinner } from "../lib/output.js";
|
|
7
7
|
import { resolveAgent, splitAgentRef, suggestAgents } from "../lib/agent-lookup.js";
|
|
@@ -11,6 +11,17 @@ import { PromptAbortedError, promptApprovals, readlineAsk } from "../lib/approva
|
|
|
11
11
|
import { withModelOverride } from "../lib/agent-models.js";
|
|
12
12
|
import { readStdinInput } from "./ai.js";
|
|
13
13
|
export function resolveRunInput(arg, inputFile, readStdin = readStdinInput) {
|
|
14
|
+
const text = readRunInput(arg, inputFile, readStdin);
|
|
15
|
+
if (text === undefined) {
|
|
16
|
+
throw new CliUsageError("Input is required: pass it as an argument, with --input-file, or via stdin.");
|
|
17
|
+
}
|
|
18
|
+
return text;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The task text from the argument, then the file, then stdin; `undefined` when none is given. A
|
|
22
|
+
* `--continue` run may have none: a connect pause is continued with an empty input (#807).
|
|
23
|
+
*/
|
|
24
|
+
export function readRunInput(arg, inputFile, readStdin = readStdinInput) {
|
|
14
25
|
if (arg)
|
|
15
26
|
return arg;
|
|
16
27
|
if (inputFile) {
|
|
@@ -29,10 +40,7 @@ export function resolveRunInput(arg, inputFile, readStdin = readStdinInput) {
|
|
|
29
40
|
throw new CliUsageError(`Input file ${inputFile} is empty.`);
|
|
30
41
|
return text;
|
|
31
42
|
}
|
|
32
|
-
|
|
33
|
-
if (piped)
|
|
34
|
-
return piped;
|
|
35
|
-
throw new CliUsageError("Input is required: pass it as an argument, with --input-file, or via stdin.");
|
|
43
|
+
return readStdin() || undefined;
|
|
36
44
|
}
|
|
37
45
|
async function withAgentSuggestions(client, agentRef, err) {
|
|
38
46
|
if (!(err instanceof BackboneApiError) || err.status !== 404 || !err.message.startsWith("Agent not found")) {
|
|
@@ -56,8 +64,9 @@ async function withAgentSuggestions(client, agentRef, err) {
|
|
|
56
64
|
*/
|
|
57
65
|
async function finishRun(command, client, target, first, opts) {
|
|
58
66
|
const agentRef = `${target.name}${target.label ? `@${target.label}` : ""}${target.model ? `#${target.model}` : ""}`;
|
|
67
|
+
const chatUrl = chatUrlFor(resolveConfig(command).baseUrl);
|
|
59
68
|
let result = first;
|
|
60
|
-
let env = buildRunEnvelope(result, agentRef);
|
|
69
|
+
let env = buildRunEnvelope(result, agentRef, chatUrl);
|
|
61
70
|
if (opts.interactive && env.status === "requires_approval") {
|
|
62
71
|
try {
|
|
63
72
|
const agent = await resolveAgent(client, target.name);
|
|
@@ -91,7 +100,7 @@ async function finishRun(command, client, target, first, opts) {
|
|
|
91
100
|
}
|
|
92
101
|
const decisions = decisionsFromAnswers(rows, answers);
|
|
93
102
|
result = await withSpinner("Continuing...", () => continueWithDecisions(client, String(agent.id), responseId, decisions, target.label, target.model, target.mode));
|
|
94
|
-
env = buildRunEnvelope(result, agentRef);
|
|
103
|
+
env = buildRunEnvelope(result, agentRef, chatUrl);
|
|
95
104
|
}
|
|
96
105
|
}
|
|
97
106
|
catch (err) {
|
|
@@ -115,12 +124,12 @@ async function finishRun(command, client, target, first, opts) {
|
|
|
115
124
|
}
|
|
116
125
|
export function makeAgentRunCommand() {
|
|
117
126
|
return new Command("run")
|
|
118
|
-
.description("Run an agent on a task (exit 3 = paused for approval, 4 = needs client tool output, 5 = incomplete)")
|
|
127
|
+
.description("Run an agent on a task (exit 3 = paused for approval, 4 = needs client tool output or a connector connected in chat, 5 = incomplete)")
|
|
119
128
|
.argument("<agent>", "Agent name or id, optionally with @label and #model")
|
|
120
|
-
.argument("[input]", "Task text (or use --input-file, or pipe it on stdin)")
|
|
129
|
+
.argument("[input]", "Task text (or use --input-file, or pipe it on stdin); optional with --continue")
|
|
121
130
|
.option("--input-file <path>", "Read the task text from a file")
|
|
122
131
|
.option("--conversation <id>", "Continue a conversation")
|
|
123
|
-
.option("--continue <responseId>", "Continue from a previous response")
|
|
132
|
+
.option("--continue <responseId>", "Continue from a previous response, with or without new input")
|
|
124
133
|
.option("--model <model>", "Run this request on another model from the agent's list (sends <agent>#<model>)")
|
|
125
134
|
.option("--mode <mode>", MODE_OPTION_HELP)
|
|
126
135
|
.option("--no-input", "Never prompt for approvals, even on a terminal")
|
|
@@ -129,12 +138,14 @@ export function makeAgentRunCommand() {
|
|
|
129
138
|
await runAction(command, async () => {
|
|
130
139
|
const agentRef = withModelOverride(agentArg, opts.model);
|
|
131
140
|
const mode = parseModeOption(opts.mode);
|
|
132
|
-
|
|
141
|
+
// A continuation may send no new turn (#807): the connect pause's `next` is exactly that command.
|
|
142
|
+
const text = opts.continue ? readRunInput(input, opts.inputFile) : resolveRunInput(input, opts.inputFile);
|
|
133
143
|
const client = getClient(command);
|
|
134
|
-
|
|
144
|
+
const turn = text === undefined ? [] : [{ type: "message", role: "user", content: text }];
|
|
145
|
+
// Without --mode the body stays exactly as before #656 (D2): input is the bare string, or [] without one.
|
|
135
146
|
const body = {
|
|
136
147
|
model: `agent/${agentRef}`,
|
|
137
|
-
input: mode ? [
|
|
148
|
+
input: mode ? [...turn, modeItem(mode)] : text ?? [],
|
|
138
149
|
stream: false,
|
|
139
150
|
};
|
|
140
151
|
if (opts.conversation)
|
package/dist/commands/ai.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { getClient, runAction } from "../lib/client.js";
|
|
4
|
-
import { isJsonOutput } from "../lib/config.js";
|
|
4
|
+
import { chatUrlFor, isJsonOutput, resolveConfig } from "../lib/config.js";
|
|
5
5
|
import { CliUsageError } from "../lib/errors.js";
|
|
6
6
|
import { withModelOverride } from "../lib/agent-models.js";
|
|
7
7
|
import { formatList, withSpinner } from "../lib/output.js";
|
|
@@ -46,7 +46,7 @@ function printResponseResult(result, command, agentRef) {
|
|
|
46
46
|
console.log(JSON.stringify(result, null, 2));
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
|
-
printRunText(buildRunEnvelope(result, agentRef));
|
|
49
|
+
printRunText(buildRunEnvelope(result, agentRef, chatUrlFor(resolveConfig(command).baseUrl)));
|
|
50
50
|
if (result?.usage) {
|
|
51
51
|
const usage = result.usage;
|
|
52
52
|
console.log(chalk.dim(`[${result.model} | ${usage.input_tokens} in / ${usage.output_tokens} out / ${usage.total_tokens} total tokens]`));
|
package/dist/lib/agent-run.d.ts
CHANGED
|
@@ -25,6 +25,19 @@ export interface PendingToolCall {
|
|
|
25
25
|
callId: string;
|
|
26
26
|
arguments: unknown;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* A connector the run waits for the user to connect, allow or reconnect in chat.2kw.ai: an open
|
|
30
|
+
* `backbone:connector_auth_request` (#807 R12). The caller never answers the connect call; the
|
|
31
|
+
* continuation re-checks access itself.
|
|
32
|
+
*/
|
|
33
|
+
export interface PendingConnection {
|
|
34
|
+
serverLabel: string;
|
|
35
|
+
host: string;
|
|
36
|
+
/** `connect`, `allow` or `reconnect`. */
|
|
37
|
+
reason: string;
|
|
38
|
+
/** The egress difference an allow is re-asked for; absent otherwise. */
|
|
39
|
+
destinations?: string[];
|
|
40
|
+
}
|
|
28
41
|
/** The end user's conversation mode (epic &59); the server matches these three values exactly. */
|
|
29
42
|
export type ConversationMode = "plan" | "ask" | "auto";
|
|
30
43
|
export declare const CONVERSATION_MODES: readonly ConversationMode[];
|
|
@@ -57,6 +70,7 @@ export interface RunEnvelope {
|
|
|
57
70
|
toolCalls: ToolCallSummary[];
|
|
58
71
|
pendingApprovals: PendingApproval[];
|
|
59
72
|
pendingToolCalls: PendingToolCall[];
|
|
73
|
+
pendingConnections: PendingConnection[];
|
|
60
74
|
incompleteReason: string | null;
|
|
61
75
|
usage: {
|
|
62
76
|
inputTokens: number;
|
|
@@ -102,7 +116,13 @@ export declare function parseAgentModel(model?: string): {
|
|
|
102
116
|
*/
|
|
103
117
|
export declare function stripControl(s: string): string;
|
|
104
118
|
export declare function shellQuote(value: string): string;
|
|
105
|
-
|
|
119
|
+
/** "Connect a (h), allow the agent to use b (h) and reconnect c (h)". */
|
|
120
|
+
export declare function connectionsSentence(connections: PendingConnection[]): string;
|
|
121
|
+
/**
|
|
122
|
+
* @param chatUrl the chat web host of the API the run went to ({@link chatUrlFor}); a connect pause's
|
|
123
|
+
* `next` sends the user to its Connectors page.
|
|
124
|
+
*/
|
|
125
|
+
export declare function buildRunEnvelope(result: ResponsesResult | undefined, agentRef?: string, chatUrl?: string): RunEnvelope;
|
|
106
126
|
/** Human-readable rendering of an envelope on stdout. Every value printed is passed through {@link stripControl}. */
|
|
107
127
|
export declare function printRunText(env: RunEnvelope): void;
|
|
108
128
|
export {};
|
package/dist/lib/agent-run.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
+
import { DEFAULT_CHAT_URL } from "./config.js";
|
|
2
3
|
import { CliUsageError } from "./errors.js";
|
|
3
4
|
export const CONVERSATION_MODES = ["plan", "ask", "auto"];
|
|
4
5
|
/** `--mode` help text, in the words of the embedded chat's mode chip (#655). */
|
|
@@ -91,7 +92,78 @@ export function shellQuote(value) {
|
|
|
91
92
|
return value;
|
|
92
93
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
93
94
|
}
|
|
94
|
-
|
|
95
|
+
/** An open connector consent request; a continuation's projection carries another status. */
|
|
96
|
+
function pendingConnectionsOf(output) {
|
|
97
|
+
return output
|
|
98
|
+
.filter((i) => i.type === "backbone:connector_auth_request" && (i.status === undefined || i.status === "in_progress"))
|
|
99
|
+
.map((i) => ({
|
|
100
|
+
serverLabel: String(i.server_label),
|
|
101
|
+
host: String(i.host),
|
|
102
|
+
reason: String(i.reason),
|
|
103
|
+
...(Array.isArray(i.destinations) && i.destinations.length > 0
|
|
104
|
+
? { destinations: i.destinations.map((d) => String(d)) }
|
|
105
|
+
: {}),
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The call ids a connect pause withholds from the caller: the call each request names, and every
|
|
110
|
+
* `mcp__<label>__connect` call for a pending connector (the model may call it more than once; one
|
|
111
|
+
* request stands for all of them).
|
|
112
|
+
*/
|
|
113
|
+
function connectCallIds(output, connections) {
|
|
114
|
+
const connectNames = new Set(connections.map((c) => `mcp__${c.serverLabel}__connect`));
|
|
115
|
+
const ids = new Set();
|
|
116
|
+
for (const item of output) {
|
|
117
|
+
if (item.type === "backbone:connector_auth_request" && (item.status === undefined || item.status === "in_progress")) {
|
|
118
|
+
ids.add(String(item.call_id));
|
|
119
|
+
}
|
|
120
|
+
else if (item.type === "function_call" && connectNames.has(String(item.name))) {
|
|
121
|
+
ids.add(String(item.call_id));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return ids;
|
|
125
|
+
}
|
|
126
|
+
function connectionPhrase(c) {
|
|
127
|
+
const target = `${c.serverLabel} (${c.host})`;
|
|
128
|
+
if (c.reason === "allow")
|
|
129
|
+
return `allow the agent to use ${target}`;
|
|
130
|
+
if (c.reason === "reconnect")
|
|
131
|
+
return `reconnect ${target}`;
|
|
132
|
+
return `connect ${target}`;
|
|
133
|
+
}
|
|
134
|
+
/** "Connect a (h), allow the agent to use b (h) and reconnect c (h)". */
|
|
135
|
+
export function connectionsSentence(connections) {
|
|
136
|
+
const phrases = connections.map(connectionPhrase);
|
|
137
|
+
const joined = phrases.length > 1 ? `${phrases.slice(0, -1).join(", ")} and ${phrases.at(-1)}` : phrases[0] ?? "";
|
|
138
|
+
return joined.charAt(0).toUpperCase() + joined.slice(1);
|
|
139
|
+
}
|
|
140
|
+
/** Joins a connect pause's instruction to the command that continues it; {@link printRunText} breaks the line there. */
|
|
141
|
+
const THEN_RUN = ", then run: ";
|
|
142
|
+
/**
|
|
143
|
+
* Output item types the envelope decodes, or that never hold a run: text, reasoning and finished
|
|
144
|
+
* connector calls. A pause on anything else (a connector approval's `mcp_approval_request`) is named.
|
|
145
|
+
*/
|
|
146
|
+
const DECODED_ITEM_TYPES = new Set([
|
|
147
|
+
"message",
|
|
148
|
+
"reasoning",
|
|
149
|
+
"function_call",
|
|
150
|
+
"function_call_output",
|
|
151
|
+
"backbone:approval_request",
|
|
152
|
+
"backbone:connector_auth_request",
|
|
153
|
+
"mcp_call",
|
|
154
|
+
"mcp_list_tools",
|
|
155
|
+
]);
|
|
156
|
+
function undecodedPause(output) {
|
|
157
|
+
const types = [...new Set(output.map((i) => String(i.type)).filter((t) => !DECODED_ITEM_TYPES.has(t)))];
|
|
158
|
+
return types.length > 0
|
|
159
|
+
? `This CLI cannot show or answer ${types.join(", ")}.`
|
|
160
|
+
: "This CLI cannot tell what the run waits for.";
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* @param chatUrl the chat web host of the API the run went to ({@link chatUrlFor}); a connect pause's
|
|
164
|
+
* `next` sends the user to its Connectors page.
|
|
165
|
+
*/
|
|
166
|
+
export function buildRunEnvelope(result, agentRef, chatUrl = DEFAULT_CHAT_URL) {
|
|
95
167
|
const output = result?.output ?? [];
|
|
96
168
|
const { agent, version } = parseAgentModel(result?.model);
|
|
97
169
|
// call_id → status of its tool output; a failed server-side tool run is marked `incomplete`.
|
|
@@ -113,7 +185,8 @@ export function buildRunEnvelope(result, agentRef) {
|
|
|
113
185
|
reason: typeof i.reason === "string" && i.reason ? i.reason : null,
|
|
114
186
|
preview: i.preview && typeof i.preview === "object" && !Array.isArray(i.preview) ? i.preview : null,
|
|
115
187
|
}));
|
|
116
|
-
const
|
|
188
|
+
const pendingConnections = pendingConnectionsOf(output);
|
|
189
|
+
const withheldCallIds = new Set([...pendingApprovals.map((a) => a.callId), ...connectCallIds(output, pendingConnections)]);
|
|
117
190
|
const toolCalls = [];
|
|
118
191
|
const pendingToolCalls = [];
|
|
119
192
|
for (const item of output) {
|
|
@@ -137,16 +210,35 @@ export function buildRunEnvelope(result, agentRef) {
|
|
|
137
210
|
status = "incomplete";
|
|
138
211
|
break;
|
|
139
212
|
case "requires_action":
|
|
140
|
-
|
|
213
|
+
// A connect pause keeps exit 4 (§8, CLI agents D14): the CLI cannot answer it either.
|
|
214
|
+
status =
|
|
215
|
+
pendingToolCalls.length > 0 || pendingConnections.length > 0 || pendingApprovals.length === 0
|
|
216
|
+
? "requires_tool_output"
|
|
217
|
+
: "requires_approval";
|
|
141
218
|
break;
|
|
142
219
|
default:
|
|
143
220
|
throw new Error(`Unexpected response status: ${result?.status}`);
|
|
144
221
|
}
|
|
145
222
|
const responseId = result?.id ?? null;
|
|
146
223
|
const ref = agentRef ?? agent;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
224
|
+
let next = null;
|
|
225
|
+
if (status === "requires_approval" && ref && responseId) {
|
|
226
|
+
next = `2kw agents decide ${shellQuote(ref)} --response ${responseId} --approve-all`;
|
|
227
|
+
}
|
|
228
|
+
else if (status === "requires_tool_output" && pendingConnections.length > 0 && pendingToolCalls.length === 0 && responseId) {
|
|
229
|
+
// Not with a relayed call beside it: a continuation without that call's output is refused.
|
|
230
|
+
// `agents run --continue` needs no input: the continuation re-checks access itself.
|
|
231
|
+
const resume = ref
|
|
232
|
+
? `${THEN_RUN}2kw agents run ${shellQuote(ref)} --continue ${shellQuote(responseId)}`
|
|
233
|
+
: `, then run the agent again with --continue ${shellQuote(responseId)}`;
|
|
234
|
+
next = `${connectionsSentence(pendingConnections)} in ${chatUrl}/connectors${resume}`;
|
|
235
|
+
}
|
|
236
|
+
else if (status === "requires_tool_output" && pendingConnections.length === 0 && pendingToolCalls.length === 0 && responseId) {
|
|
237
|
+
// Paused on nothing the envelope decodes: say so, rather than exit 4 without a word.
|
|
238
|
+
next =
|
|
239
|
+
`${undecodedPause(output)} Response ${responseId} stays paused: answer it in ${chatUrl}/ ` +
|
|
240
|
+
"or from a client that continues it by previous_response_id.";
|
|
241
|
+
}
|
|
150
242
|
return {
|
|
151
243
|
status,
|
|
152
244
|
mode: responseMode(result),
|
|
@@ -158,6 +250,7 @@ export function buildRunEnvelope(result, agentRef) {
|
|
|
158
250
|
toolCalls,
|
|
159
251
|
pendingApprovals,
|
|
160
252
|
pendingToolCalls,
|
|
253
|
+
pendingConnections,
|
|
161
254
|
incompleteReason: result?.incomplete_details?.reason ?? null,
|
|
162
255
|
usage: result?.usage
|
|
163
256
|
? { inputTokens: result.usage.input_tokens ?? 0, outputTokens: result.usage.output_tokens ?? 0 }
|
|
@@ -192,8 +285,26 @@ export function printRunText(env) {
|
|
|
192
285
|
console.log(`\nDecide with:\n ${s(env.next)}`);
|
|
193
286
|
}
|
|
194
287
|
else if (env.status === "requires_tool_output") {
|
|
195
|
-
|
|
196
|
-
|
|
288
|
+
if (env.pendingConnections.length) {
|
|
289
|
+
console.log(chalk.yellow("\nPaused: the agent needs you to connect in chat:"));
|
|
290
|
+
env.pendingConnections.forEach((c) => {
|
|
291
|
+
console.log(` - ${s(connectionsSentence([c]))}`);
|
|
292
|
+
if (c.destinations?.length)
|
|
293
|
+
console.log(chalk.dim(` new destinations: ${s(c.destinations.join(", "))}`));
|
|
294
|
+
});
|
|
295
|
+
// The continue command on its own line, ready to copy.
|
|
296
|
+
if (env.next)
|
|
297
|
+
console.log(`\n${s(env.next.replace(THEN_RUN, ", then run:\n "))}`);
|
|
298
|
+
}
|
|
299
|
+
if (env.pendingToolCalls.length) {
|
|
300
|
+
console.log(chalk.yellow("\nPaused: the agent needs client-side tool output this CLI cannot provide:"));
|
|
301
|
+
env.pendingToolCalls.forEach((c) => console.log(` - ${s(c.tool)} (${s(c.callId)})`));
|
|
302
|
+
}
|
|
303
|
+
if (!env.pendingConnections.length && !env.pendingToolCalls.length) {
|
|
304
|
+
console.log(chalk.yellow("\nPaused on something this CLI cannot show:"));
|
|
305
|
+
if (env.next)
|
|
306
|
+
console.log(` ${s(env.next)}`);
|
|
307
|
+
}
|
|
197
308
|
}
|
|
198
309
|
else if (env.status === "incomplete") {
|
|
199
310
|
console.log(chalk.yellow(`\nIncomplete: ${s(env.incompleteReason ?? "unknown reason")}`));
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -52,6 +52,14 @@ export declare const DEFAULT_BASE_URL: string;
|
|
|
52
52
|
*/
|
|
53
53
|
export declare const DEFAULT_AUTH_URL: string;
|
|
54
54
|
export declare function defaultAuthUrlFor(baseUrl: string): string;
|
|
55
|
+
/** Fallback chat.2kw.ai origin when the API host is not one we recognise. */
|
|
56
|
+
export declare const DEFAULT_CHAT_URL = "https://chat.2kw.ai";
|
|
57
|
+
/**
|
|
58
|
+
* The chat web host that belongs to an API base URL, where a member connects a connector
|
|
59
|
+
* (`<chat>/connectors`). `AI_2KW_CHAT_URL` overrides the mapping, for deployments the map
|
|
60
|
+
* does not know; a trailing slash is dropped so callers can append a path.
|
|
61
|
+
*/
|
|
62
|
+
export declare function chatUrlFor(baseUrl: string, env?: NodeJS.ProcessEnv): string;
|
|
55
63
|
declare const store: Conf<BackboneConfigStore>;
|
|
56
64
|
export { store };
|
|
57
65
|
export declare function validateContextName(name: string): void;
|
package/dist/lib/config.js
CHANGED
|
@@ -36,6 +36,34 @@ export function defaultAuthUrlFor(baseUrl) {
|
|
|
36
36
|
return DEFAULT_AUTH_URL;
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
/** Fallback chat.2kw.ai origin when the API host is not one we recognise. */
|
|
40
|
+
export const DEFAULT_CHAT_URL = "https://chat.2kw.ai";
|
|
41
|
+
/** Known API-host → chat web host mappings; a connect pause points the user there (#807 R11). */
|
|
42
|
+
const CHAT_URL_BY_API_HOST = {
|
|
43
|
+
"api.2kw.ai": "https://chat.2kw.ai",
|
|
44
|
+
"api-dev.2kw.ai": "https://chat-dev.2kw.ai",
|
|
45
|
+
"backbone.manfred-kunze.dev": "https://chat.2kw.ai",
|
|
46
|
+
"localhost:8080": "http://localhost:3000",
|
|
47
|
+
"127.0.0.1:8080": "http://localhost:3000",
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* The chat web host that belongs to an API base URL, where a member connects a connector
|
|
51
|
+
* (`<chat>/connectors`). `AI_2KW_CHAT_URL` overrides the mapping, for deployments the map
|
|
52
|
+
* does not know; a trailing slash is dropped so callers can append a path.
|
|
53
|
+
*/
|
|
54
|
+
export function chatUrlFor(baseUrl, env = process.env) {
|
|
55
|
+
const override = env.AI_2KW_CHAT_URL?.trim();
|
|
56
|
+
if (override)
|
|
57
|
+
return override.replace(/\/+$/, "");
|
|
58
|
+
try {
|
|
59
|
+
const host = new URL(baseUrl).host;
|
|
60
|
+
// hasOwn, as in defaultAuthUrlFor: a host named after an Object.prototype key must not resolve.
|
|
61
|
+
return Object.hasOwn(CHAT_URL_BY_API_HOST, host) ? CHAT_URL_BY_API_HOST[host] : DEFAULT_CHAT_URL;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return DEFAULT_CHAT_URL;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
39
67
|
// One-shot flag so we only print the deprecation warning once per process,
|
|
40
68
|
// even if resolveConfig is called multiple times across commands.
|
|
41
69
|
let legacyEnvWarned = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@2kw/ai",
|
|
3
|
-
"version": "6.3.0-dev.
|
|
3
|
+
"version": "6.3.0-dev.84",
|
|
4
4
|
"description": "CLI for 2kw.ai — schema-driven document extraction, an OpenAI-compatible EU LLM gateway, transcription, prompts, datasets, and experiments from your terminal or agentic workflows. Ships as 2kw, backbone, and bb.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|