@ishlabs/cli 0.9.0 → 0.11.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 +54 -5
- package/dist/commands/ask.d.ts +12 -0
- package/dist/commands/ask.js +127 -2
- package/dist/commands/chat.d.ts +17 -0
- package/dist/commands/chat.js +655 -0
- package/dist/commands/iteration.js +134 -14
- package/dist/commands/secret.d.ts +20 -0
- package/dist/commands/secret.js +246 -0
- package/dist/commands/study-run.d.ts +38 -0
- package/dist/commands/study-run.js +199 -80
- package/dist/commands/study-tester.js +17 -2
- package/dist/commands/study.js +309 -37
- package/dist/commands/workspace.js +81 -0
- package/dist/config.d.ts +3 -0
- package/dist/connect.d.ts +3 -0
- package/dist/connect.js +346 -22
- package/dist/index.js +64 -6
- package/dist/lib/alias-hydrate.d.ts +42 -0
- package/dist/lib/alias-hydrate.js +175 -0
- package/dist/lib/alias-store.d.ts +1 -0
- package/dist/lib/alias-store.js +28 -1
- package/dist/lib/auth.js +4 -2
- package/dist/lib/chat-endpoint-formatters.d.ts +74 -0
- package/dist/lib/chat-endpoint-formatters.js +154 -0
- package/dist/lib/chat-endpoint-templates.d.ts +35 -0
- package/dist/lib/chat-endpoint-templates.js +210 -0
- package/dist/lib/command-helpers.d.ts +18 -0
- package/dist/lib/command-helpers.js +105 -3
- package/dist/lib/docs.js +641 -17
- package/dist/lib/modality.d.ts +42 -0
- package/dist/lib/modality.js +192 -0
- package/dist/lib/output.d.ts +41 -0
- package/dist/lib/output.js +453 -19
- package/dist/lib/paths.d.ts +1 -0
- package/dist/lib/paths.js +3 -0
- package/dist/lib/skill-content.d.ts +18 -0
- package/dist/lib/skill-content.js +223 -12
- package/dist/lib/types.d.ts +15 -0
- package/package.json +2 -2
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort alias-cache hydration on alias-miss (Pattern F).
|
|
3
|
+
*
|
|
4
|
+
* The CLI persists aliases to ``~/.ish/aliases.json``, so the cache survives
|
|
5
|
+
* across processes. Where it bites: the file is missing/empty (fresh install,
|
|
6
|
+
* `rm ~/.ish/aliases.json`, agent running in a sandbox with a fresh
|
|
7
|
+
* `ISH_HOME`) and the agent has an alias from a prior process or the docs.
|
|
8
|
+
*
|
|
9
|
+
* ``resolveIdAsync(input, client, hints?)`` mirrors the sync ``resolveId``
|
|
10
|
+
* contract but, on alias-miss, attempts a single ``GET /list`` to repopulate
|
|
11
|
+
* the cache before retrying. The hydrate is BEST-EFFORT: a failing list
|
|
12
|
+
* call is swallowed and the canonical "Unknown alias" error fires with the
|
|
13
|
+
* actionable suggestion in the message.
|
|
14
|
+
*
|
|
15
|
+
* For prefixes whose list endpoint requires a parent ID (study/iteration/
|
|
16
|
+
* ask/etc), the caller passes ``hints`` carrying the parent (workspaceId,
|
|
17
|
+
* studyId). Without a parent we skip the hydrate — global N+1 fan-out is
|
|
18
|
+
* too expensive for a papercut.
|
|
19
|
+
*/
|
|
20
|
+
import { ALIAS_PREFIX, resolveId, tagAlias } from "./alias-store.js";
|
|
21
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
22
|
+
const ALIAS_RE = /^[a-z]+-[0-9a-f]{3,}$|^[a-z]+\d{2,}$/;
|
|
23
|
+
function aliasPrefix(value) {
|
|
24
|
+
if (!ALIAS_RE.test(value))
|
|
25
|
+
return null;
|
|
26
|
+
const m = value.match(/^([a-z]+)/);
|
|
27
|
+
return m ? m[1] : null;
|
|
28
|
+
}
|
|
29
|
+
function isAliasShape(value) {
|
|
30
|
+
return ALIAS_RE.test(value);
|
|
31
|
+
}
|
|
32
|
+
function isUuid(value) {
|
|
33
|
+
return UUID_RE.test(value);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Try to resolve a parent ID — accepts either a UUID or a known alias. Used
|
|
37
|
+
* to extract ``workspaceId`` / ``studyId`` from hints before we fan out to a
|
|
38
|
+
* scoped list endpoint. Returns ``null`` if the value can't be resolved
|
|
39
|
+
* cheaply (e.g. it's an alias that's also missing from the cache).
|
|
40
|
+
*/
|
|
41
|
+
function resolveParent(value) {
|
|
42
|
+
if (!value)
|
|
43
|
+
return null;
|
|
44
|
+
if (isUuid(value))
|
|
45
|
+
return value;
|
|
46
|
+
// Try the sync resolver but never throw.
|
|
47
|
+
try {
|
|
48
|
+
return resolveId(value);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Hit a list endpoint and tag every returned ID under the given prefix.
|
|
56
|
+
* Silently swallows network/auth failures — we only ever block the
|
|
57
|
+
* underlying call when the alias is genuinely unknown after hydrate.
|
|
58
|
+
*/
|
|
59
|
+
async function hydrateList(client, prefix, path, params) {
|
|
60
|
+
let data;
|
|
61
|
+
try {
|
|
62
|
+
data = await client.get(path, params);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
let items = [];
|
|
68
|
+
if (Array.isArray(data)) {
|
|
69
|
+
items = data;
|
|
70
|
+
}
|
|
71
|
+
else if (typeof data === "object" && data !== null && "items" in data) {
|
|
72
|
+
const inner = data.items;
|
|
73
|
+
if (Array.isArray(inner))
|
|
74
|
+
items = inner;
|
|
75
|
+
}
|
|
76
|
+
for (const item of items) {
|
|
77
|
+
if (typeof item !== "object" || item === null)
|
|
78
|
+
continue;
|
|
79
|
+
const id = item.id;
|
|
80
|
+
if (typeof id === "string" && id.length > 0) {
|
|
81
|
+
try {
|
|
82
|
+
tagAlias(prefix, id);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Ignore — bad UUIDs are not catastrophic.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Best-effort hydrate of the alias cache for ``alias``'s entity type. Returns
|
|
92
|
+
* silently on success or any failure — the caller checks the cache after
|
|
93
|
+
* this returns.
|
|
94
|
+
*/
|
|
95
|
+
export async function hydrateForAlias(client, alias, hints = {}) {
|
|
96
|
+
const prefix = aliasPrefix(alias);
|
|
97
|
+
if (!prefix)
|
|
98
|
+
return;
|
|
99
|
+
const ws = resolveParent(hints.workspaceId);
|
|
100
|
+
const study = resolveParent(hints.studyId);
|
|
101
|
+
switch (prefix) {
|
|
102
|
+
case ALIAS_PREFIX.workspace: {
|
|
103
|
+
// Top-level — no parent.
|
|
104
|
+
await hydrateList(client, ALIAS_PREFIX.workspace, "/products");
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
case ALIAS_PREFIX.study: {
|
|
108
|
+
if (!ws)
|
|
109
|
+
return;
|
|
110
|
+
await hydrateList(client, ALIAS_PREFIX.study, `/products/${ws}/studies`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
case ALIAS_PREFIX.iteration: {
|
|
114
|
+
if (!study)
|
|
115
|
+
return;
|
|
116
|
+
await hydrateList(client, ALIAS_PREFIX.iteration, `/studies/${study}/iterations`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
case ALIAS_PREFIX.testerProfile: {
|
|
120
|
+
if (!ws)
|
|
121
|
+
return;
|
|
122
|
+
await hydrateList(client, ALIAS_PREFIX.testerProfile, "/tester-profiles", { workspace_id: ws, type: "all", limit: "200" });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
case ALIAS_PREFIX.ask: {
|
|
126
|
+
if (!ws)
|
|
127
|
+
return;
|
|
128
|
+
await hydrateList(client, ALIAS_PREFIX.ask, `/products/${ws}/asks`);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
case ALIAS_PREFIX.chatEndpoint: {
|
|
132
|
+
if (!ws)
|
|
133
|
+
return;
|
|
134
|
+
await hydrateList(client, ALIAS_PREFIX.chatEndpoint, `/products/${ws}/chatbot-endpoints`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
// No cheap single-call hydrate for the rest:
|
|
138
|
+
// tester (`t-`) — scoped to iteration
|
|
139
|
+
// ask round (`r-`) — nested on the ask
|
|
140
|
+
// audience source (`tps-`) — fetched per-id
|
|
141
|
+
// simulation config (`c-`) — no list endpoint yet
|
|
142
|
+
default:
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Async sibling of ``resolveId``: same UUID-or-alias contract, but on
|
|
148
|
+
* alias-miss attempts a best-effort hydrate via the matching list endpoint
|
|
149
|
+
* before retrying. Falls back to the canonical "Unknown alias" error
|
|
150
|
+
* (with named list-command suggestion) when the alias is still missing
|
|
151
|
+
* after the hydrate.
|
|
152
|
+
*
|
|
153
|
+
* Use this at command-handler entry points where the agent supplies an
|
|
154
|
+
* alias and the same call carries enough context (workspace / study) to
|
|
155
|
+
* scope the hydrate cheaply.
|
|
156
|
+
*/
|
|
157
|
+
export async function resolveIdAsync(input, client, hints = {}) {
|
|
158
|
+
if (isUuid(input))
|
|
159
|
+
return input;
|
|
160
|
+
if (!isAliasShape(input)) {
|
|
161
|
+
// Fall through to the sync resolver so the "Invalid ID" guidance fires.
|
|
162
|
+
return resolveId(input);
|
|
163
|
+
}
|
|
164
|
+
// Cheap-path: alias already in store.
|
|
165
|
+
try {
|
|
166
|
+
return resolveId(input);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// Cache miss — attempt best-effort hydrate.
|
|
170
|
+
await hydrateForAlias(client, input, hints);
|
|
171
|
+
// Retry — sync resolver re-reads ``aliases.json`` from disk on every
|
|
172
|
+
// call (see loadAliases), so the freshly tagged entries are visible.
|
|
173
|
+
return resolveId(input);
|
|
174
|
+
}
|
|
175
|
+
}
|
package/dist/lib/alias-store.js
CHANGED
|
@@ -20,6 +20,7 @@ export const ALIAS_PREFIX = {
|
|
|
20
20
|
job: "j",
|
|
21
21
|
ask: "a",
|
|
22
22
|
askRound: "r",
|
|
23
|
+
chatEndpoint: "ep",
|
|
23
24
|
};
|
|
24
25
|
/** Format a number with zero-padding (minimum 2 digits). */
|
|
25
26
|
function padNum(n) {
|
|
@@ -110,6 +111,32 @@ export function deterministicAlias(prefix, uuid) {
|
|
|
110
111
|
}
|
|
111
112
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
112
113
|
const ALIAS_RE = /^[a-z]+-[0-9a-f]{3,}$|^[a-z]+\d{2,}$/;
|
|
114
|
+
/**
|
|
115
|
+
* Suggested `ish ... list` command per alias prefix. Surfaced in the
|
|
116
|
+
* unknown-alias error (Pattern F) so the agent doesn't have to guess
|
|
117
|
+
* which list command to run.
|
|
118
|
+
*/
|
|
119
|
+
const HYDRATE_HINT = {
|
|
120
|
+
w: "ish workspace list",
|
|
121
|
+
s: "ish study list",
|
|
122
|
+
i: "ish iteration list --study <study-id>",
|
|
123
|
+
tp: "ish profile list",
|
|
124
|
+
tps: "ish source list",
|
|
125
|
+
t: "ish tester get <tester-id>",
|
|
126
|
+
c: "ish config list",
|
|
127
|
+
a: "ish ask list",
|
|
128
|
+
r: "ish ask get <ask-id>",
|
|
129
|
+
ep: "ish chat endpoint list",
|
|
130
|
+
// Legacy two-letter prefixes the deterministic generator may have
|
|
131
|
+
// produced before; defaults below cover anything else.
|
|
132
|
+
};
|
|
133
|
+
function hintForPrefix(alias) {
|
|
134
|
+
// Pull the leading alpha run, which is the prefix.
|
|
135
|
+
const m = alias.match(/^([a-z]+)/);
|
|
136
|
+
if (!m)
|
|
137
|
+
return "the matching list command";
|
|
138
|
+
return HYDRATE_HINT[m[1]] ?? "the matching list command";
|
|
139
|
+
}
|
|
113
140
|
/**
|
|
114
141
|
* Resolve a short alias to a full UUID, or validate and pass through a full UUID.
|
|
115
142
|
*
|
|
@@ -130,7 +157,7 @@ export function resolveId(input) {
|
|
|
130
157
|
const uuid = aliases[input];
|
|
131
158
|
if (uuid)
|
|
132
159
|
return uuid;
|
|
133
|
-
throw new Error(`Unknown alias "${input}". Run
|
|
160
|
+
throw new Error(`Unknown alias "${input}". Run \`${hintForPrefix(input)}\` first to generate aliases.`);
|
|
134
161
|
}
|
|
135
162
|
// 3. Anything else — fail with helpful guidance
|
|
136
163
|
throw new Error(`Invalid ID "${input}". Use a short alias (e.g. w-a3f, s-b2c) or a full UUID.\n` +
|
package/dist/lib/auth.js
CHANGED
|
@@ -24,8 +24,10 @@ async function verifyToken(token, apiUrl) {
|
|
|
24
24
|
return resp.status !== 401 && resp.status !== 403;
|
|
25
25
|
}
|
|
26
26
|
catch {
|
|
27
|
-
// Network
|
|
28
|
-
|
|
27
|
+
// Network blip on the best-effort probe. The subsequent API call will
|
|
28
|
+
// surface the real auth failure (with a proper exit code 3) if there
|
|
29
|
+
// is one, so don't pollute stderr on every command — it fired on
|
|
30
|
+
// every successful run during Phase A (Pattern F / C4-finding-5).
|
|
29
31
|
return true;
|
|
30
32
|
}
|
|
31
33
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lean-vs-verbose projection for ChatbotEndpointResponse.
|
|
3
|
+
*
|
|
4
|
+
* The backend returns a nested camelCase shape (id, name, productId, config,
|
|
5
|
+
* isTunnelBacked, createdAt, updatedAt). The lean projection keeps only the
|
|
6
|
+
* fields an agent typically branches on: id/alias/name, transport, the
|
|
7
|
+
* outgoing url + method, the incoming messagePath, slot/reference counts, and
|
|
8
|
+
* isTunnelBacked. `--verbose` (or piped) passes the raw response.
|
|
9
|
+
*
|
|
10
|
+
* Slots-only model: `incoming.slots` and `incoming.references` are typed
|
|
11
|
+
* binding lists. Each slot carries `{containerPath, kind, labelPath, idPath}`;
|
|
12
|
+
* each reference carries `{containerPath, labelPath, urlPath}`. Legacy fields
|
|
13
|
+
* (`optionsPath`, `formRequestPath`, `cardsPath`, `artifactsPath`,
|
|
14
|
+
* `suggestedFollowupsPath`, plus the parallel `slotsContainerPaths` /
|
|
15
|
+
* `slotsKindHints` / `slotsLabelPaths` / `slotsIdPaths` /
|
|
16
|
+
* `referencesContainerPaths` arrays) are gone — anything interactive is a
|
|
17
|
+
* slot tagged with `kind`; anything passive is a reference.
|
|
18
|
+
*/
|
|
19
|
+
export interface OutgoingHttp {
|
|
20
|
+
url?: string;
|
|
21
|
+
method?: string;
|
|
22
|
+
mode?: string;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
}
|
|
25
|
+
export interface SlotBinding {
|
|
26
|
+
containerPath: string;
|
|
27
|
+
kind?: "alternatives" | "form" | "text" | null;
|
|
28
|
+
labelPath?: string | null;
|
|
29
|
+
idPath?: string | null;
|
|
30
|
+
}
|
|
31
|
+
export interface ReferenceBinding {
|
|
32
|
+
containerPath: string;
|
|
33
|
+
labelPath?: string | null;
|
|
34
|
+
urlPath?: string | null;
|
|
35
|
+
}
|
|
36
|
+
export interface IncomingHttp {
|
|
37
|
+
messagePath?: string;
|
|
38
|
+
conversationIdPath?: string | null;
|
|
39
|
+
endOfConversationPath?: string | null;
|
|
40
|
+
errorPath?: string | null;
|
|
41
|
+
toolCallsPath?: string | null;
|
|
42
|
+
tokenUsagePath?: string | null;
|
|
43
|
+
slots?: SlotBinding[];
|
|
44
|
+
references?: ReferenceBinding[];
|
|
45
|
+
responseStub?: unknown;
|
|
46
|
+
[key: string]: unknown;
|
|
47
|
+
}
|
|
48
|
+
export interface StreamingSettings {
|
|
49
|
+
eventFormat?: "openai" | "anthropic" | "raw";
|
|
50
|
+
deltaPath?: string | null;
|
|
51
|
+
terminalEvent?: string | null;
|
|
52
|
+
maxWaitSeconds?: number;
|
|
53
|
+
}
|
|
54
|
+
export interface ChatbotEndpointConfig {
|
|
55
|
+
transport?: string;
|
|
56
|
+
outgoing?: OutgoingHttp;
|
|
57
|
+
incoming?: IncomingHttp;
|
|
58
|
+
streaming?: StreamingSettings | null;
|
|
59
|
+
asyncPoll?: unknown;
|
|
60
|
+
isTunnelBacked?: boolean;
|
|
61
|
+
[key: string]: unknown;
|
|
62
|
+
}
|
|
63
|
+
export interface ChatbotEndpointRow {
|
|
64
|
+
id?: string;
|
|
65
|
+
name?: string;
|
|
66
|
+
productId?: string;
|
|
67
|
+
config?: ChatbotEndpointConfig;
|
|
68
|
+
isTunnelBacked?: boolean;
|
|
69
|
+
[key: string]: unknown;
|
|
70
|
+
}
|
|
71
|
+
/** Return the round-trippable envelope used by `endpoint get --verbose`. */
|
|
72
|
+
export declare function envelopeFromRow(row: ChatbotEndpointRow): Record<string, unknown>;
|
|
73
|
+
export declare function formatChatEndpointList(rows: ChatbotEndpointRow[], json: boolean, verbose: boolean): void;
|
|
74
|
+
export declare function formatChatEndpointDetail(row: ChatbotEndpointRow, json: boolean, verbose: boolean): void;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lean-vs-verbose projection for ChatbotEndpointResponse.
|
|
3
|
+
*
|
|
4
|
+
* The backend returns a nested camelCase shape (id, name, productId, config,
|
|
5
|
+
* isTunnelBacked, createdAt, updatedAt). The lean projection keeps only the
|
|
6
|
+
* fields an agent typically branches on: id/alias/name, transport, the
|
|
7
|
+
* outgoing url + method, the incoming messagePath, slot/reference counts, and
|
|
8
|
+
* isTunnelBacked. `--verbose` (or piped) passes the raw response.
|
|
9
|
+
*
|
|
10
|
+
* Slots-only model: `incoming.slots` and `incoming.references` are typed
|
|
11
|
+
* binding lists. Each slot carries `{containerPath, kind, labelPath, idPath}`;
|
|
12
|
+
* each reference carries `{containerPath, labelPath, urlPath}`. Legacy fields
|
|
13
|
+
* (`optionsPath`, `formRequestPath`, `cardsPath`, `artifactsPath`,
|
|
14
|
+
* `suggestedFollowupsPath`, plus the parallel `slotsContainerPaths` /
|
|
15
|
+
* `slotsKindHints` / `slotsLabelPaths` / `slotsIdPaths` /
|
|
16
|
+
* `referencesContainerPaths` arrays) are gone — anything interactive is a
|
|
17
|
+
* slot tagged with `kind`; anything passive is a reference.
|
|
18
|
+
*/
|
|
19
|
+
import { tagAlias, ALIAS_PREFIX } from "./alias-store.js";
|
|
20
|
+
import { output, printTable } from "./output.js";
|
|
21
|
+
function leanRow(row) {
|
|
22
|
+
const cfg = row.config ?? {};
|
|
23
|
+
const out = {};
|
|
24
|
+
if (row.id)
|
|
25
|
+
out.alias = tagAlias(ALIAS_PREFIX.chatEndpoint, row.id);
|
|
26
|
+
if (row.id)
|
|
27
|
+
out.id = row.id;
|
|
28
|
+
if (row.name)
|
|
29
|
+
out.name = row.name;
|
|
30
|
+
if (cfg.transport)
|
|
31
|
+
out.transport = cfg.transport;
|
|
32
|
+
out.is_tunnel_backed = Boolean(row.isTunnelBacked);
|
|
33
|
+
if (cfg.outgoing?.url)
|
|
34
|
+
out.url = cfg.outgoing.url;
|
|
35
|
+
if (cfg.outgoing?.method)
|
|
36
|
+
out.method = cfg.outgoing.method;
|
|
37
|
+
if (cfg.outgoing?.mode)
|
|
38
|
+
out.mode = cfg.outgoing.mode;
|
|
39
|
+
if (cfg.incoming?.messagePath)
|
|
40
|
+
out.message_path = cfg.incoming.messagePath;
|
|
41
|
+
out.slots = Array.isArray(cfg.incoming?.slots) ? cfg.incoming.slots.length : 0;
|
|
42
|
+
out.references = Array.isArray(cfg.incoming?.references)
|
|
43
|
+
? cfg.incoming.references.length
|
|
44
|
+
: 0;
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
/** Return the round-trippable envelope used by `endpoint get --verbose`. */
|
|
48
|
+
export function envelopeFromRow(row) {
|
|
49
|
+
return {
|
|
50
|
+
id: row.id,
|
|
51
|
+
name: row.name,
|
|
52
|
+
isTunnelBacked: Boolean(row.isTunnelBacked),
|
|
53
|
+
config: row.config ?? {},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function formatChatEndpointList(rows, json, verbose) {
|
|
57
|
+
if (json) {
|
|
58
|
+
if (verbose) {
|
|
59
|
+
output(rows, true);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
output(rows.map(leanRow), true);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (rows.length === 0) {
|
|
66
|
+
console.log("No chatbot endpoints.");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const lean = rows.map(leanRow);
|
|
70
|
+
printTable(["#", "NAME", "TRANSPORT", "URL", "METHOD", "MODE", "TUNNEL"], lean.map((r) => [
|
|
71
|
+
String(r.alias ?? r.id ?? ""),
|
|
72
|
+
String(r.name ?? ""),
|
|
73
|
+
String(r.transport ?? "-"),
|
|
74
|
+
String(r.url ?? "-"),
|
|
75
|
+
String(r.method ?? "-"),
|
|
76
|
+
String(r.mode ?? "-"),
|
|
77
|
+
r.is_tunnel_backed ? "yes" : "no",
|
|
78
|
+
]));
|
|
79
|
+
}
|
|
80
|
+
function formatSlotLine(slot) {
|
|
81
|
+
const kindLabel = slot.kind ?? "auto";
|
|
82
|
+
const subParts = [];
|
|
83
|
+
if (slot.labelPath)
|
|
84
|
+
subParts.push(`label=${slot.labelPath}`);
|
|
85
|
+
if (slot.idPath)
|
|
86
|
+
subParts.push(`id=${slot.idPath}`);
|
|
87
|
+
const tail = subParts.length > 0 ? ` (${subParts.join(", ")})` : "";
|
|
88
|
+
return ` ${slot.containerPath} [${kindLabel}]${tail}`;
|
|
89
|
+
}
|
|
90
|
+
function formatReferenceLine(ref) {
|
|
91
|
+
const subParts = [];
|
|
92
|
+
if (ref.labelPath)
|
|
93
|
+
subParts.push(`label=${ref.labelPath}`);
|
|
94
|
+
if (ref.urlPath)
|
|
95
|
+
subParts.push(`url=${ref.urlPath}`);
|
|
96
|
+
const tail = subParts.length > 0 ? ` (${subParts.join(", ")})` : "";
|
|
97
|
+
return ` ${ref.containerPath}${tail}`;
|
|
98
|
+
}
|
|
99
|
+
export function formatChatEndpointDetail(row, json, verbose) {
|
|
100
|
+
if (json) {
|
|
101
|
+
if (verbose) {
|
|
102
|
+
output(envelopeFromRow(row), true);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
output(leanRow(row), true);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const cfg = row.config ?? {};
|
|
109
|
+
const alias = row.id ? tagAlias(ALIAS_PREFIX.chatEndpoint, row.id) : "-";
|
|
110
|
+
console.log(`${row.name || "Untitled"} (${alias})`);
|
|
111
|
+
const meta = [];
|
|
112
|
+
if (cfg.transport)
|
|
113
|
+
meta.push(String(cfg.transport));
|
|
114
|
+
if (row.isTunnelBacked)
|
|
115
|
+
meta.push("tunnel-backed");
|
|
116
|
+
if (meta.length > 0)
|
|
117
|
+
console.log(meta.join(" · "));
|
|
118
|
+
if (cfg.outgoing) {
|
|
119
|
+
console.log("");
|
|
120
|
+
console.log(` URL ${cfg.outgoing.url ?? "-"}`);
|
|
121
|
+
console.log(` Method ${cfg.outgoing.method ?? "-"}`);
|
|
122
|
+
console.log(` Mode ${cfg.outgoing.mode ?? "-"}`);
|
|
123
|
+
}
|
|
124
|
+
if (cfg.incoming) {
|
|
125
|
+
console.log("");
|
|
126
|
+
console.log(` Message path ${cfg.incoming.messagePath ?? "-"}`);
|
|
127
|
+
if (cfg.incoming.conversationIdPath) {
|
|
128
|
+
console.log(` Session path ${cfg.incoming.conversationIdPath}`);
|
|
129
|
+
}
|
|
130
|
+
if (cfg.incoming.errorPath) {
|
|
131
|
+
console.log(` Error path ${cfg.incoming.errorPath}`);
|
|
132
|
+
}
|
|
133
|
+
const slots = Array.isArray(cfg.incoming.slots) ? cfg.incoming.slots : [];
|
|
134
|
+
console.log(` Slots ${slots.length}`);
|
|
135
|
+
for (const slot of slots) {
|
|
136
|
+
console.log(formatSlotLine(slot));
|
|
137
|
+
}
|
|
138
|
+
const refs = Array.isArray(cfg.incoming.references) ? cfg.incoming.references : [];
|
|
139
|
+
console.log(` References ${refs.length}`);
|
|
140
|
+
for (const ref of refs) {
|
|
141
|
+
console.log(formatReferenceLine(ref));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (cfg.transport === "streaming" && cfg.streaming) {
|
|
145
|
+
console.log("");
|
|
146
|
+
console.log(` Streaming ${cfg.streaming.eventFormat ?? "openai"}`);
|
|
147
|
+
if (cfg.streaming.deltaPath) {
|
|
148
|
+
console.log(` delta_path ${cfg.streaming.deltaPath}`);
|
|
149
|
+
}
|
|
150
|
+
if (cfg.streaming.terminalEvent) {
|
|
151
|
+
console.log(` terminal ${cfg.streaming.terminalEvent}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hand-curated `ChatbotEndpointConfig` templates for `ish chat endpoint init
|
|
3
|
+
* --template <name>`.
|
|
4
|
+
*
|
|
5
|
+
* Each template is a known-good wire shape an agent can drop straight into
|
|
6
|
+
* `create_chatbot_endpoint` — derived from public docs for the named
|
|
7
|
+
* provider. Auth / API key values are placeholder secret refs
|
|
8
|
+
* (`{{secret:NAME}}`); the agent stores the real value via
|
|
9
|
+
* `ish secret set` before testing.
|
|
10
|
+
*
|
|
11
|
+
* Templates are intentionally minimal:
|
|
12
|
+
* - `transport`, `outgoing`, `incoming` (slots-only).
|
|
13
|
+
* - No `retry` block — defaults are fine.
|
|
14
|
+
* - No `asyncPoll` — none of the listed providers use it.
|
|
15
|
+
* - `streaming` only when the provider's default flow is SSE-shaped
|
|
16
|
+
* (we currently keep all templates in `sync` mode; agents who want
|
|
17
|
+
* streaming flip the transport themselves after init).
|
|
18
|
+
*
|
|
19
|
+
* Slots / references are populated when the provider documents a stable
|
|
20
|
+
* structured-output container (e.g. Bot Framework's `suggestedActions`,
|
|
21
|
+
* Watson Assistant's `output.generic[]`). Where the provider is pure text
|
|
22
|
+
* (vanilla OpenAI / Anthropic chat-completions), the lists stay empty —
|
|
23
|
+
* the runtime auto-classifier handles any structured content the bot
|
|
24
|
+
* decides to emit per turn.
|
|
25
|
+
*/
|
|
26
|
+
import type { ChatbotEndpointConfig } from "./chat-endpoint-formatters.js";
|
|
27
|
+
export type ChatEndpointTemplateName = "openai" | "anthropic" | "voiceflow" | "dialogflow-cx" | "botframework";
|
|
28
|
+
export interface ChatEndpointTemplate {
|
|
29
|
+
name: ChatEndpointTemplateName;
|
|
30
|
+
description: string;
|
|
31
|
+
config: ChatbotEndpointConfig;
|
|
32
|
+
}
|
|
33
|
+
export declare const TEMPLATE_NAMES: ChatEndpointTemplateName[];
|
|
34
|
+
export declare function getChatEndpointTemplate(name: string): ChatEndpointTemplate | undefined;
|
|
35
|
+
export declare function listChatEndpointTemplates(): ChatEndpointTemplate[];
|