@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.
Files changed (39) hide show
  1. package/README.md +54 -5
  2. package/dist/commands/ask.d.ts +12 -0
  3. package/dist/commands/ask.js +127 -2
  4. package/dist/commands/chat.d.ts +17 -0
  5. package/dist/commands/chat.js +655 -0
  6. package/dist/commands/iteration.js +134 -14
  7. package/dist/commands/secret.d.ts +20 -0
  8. package/dist/commands/secret.js +246 -0
  9. package/dist/commands/study-run.d.ts +38 -0
  10. package/dist/commands/study-run.js +199 -80
  11. package/dist/commands/study-tester.js +17 -2
  12. package/dist/commands/study.js +309 -37
  13. package/dist/commands/workspace.js +81 -0
  14. package/dist/config.d.ts +3 -0
  15. package/dist/connect.d.ts +3 -0
  16. package/dist/connect.js +346 -22
  17. package/dist/index.js +64 -6
  18. package/dist/lib/alias-hydrate.d.ts +42 -0
  19. package/dist/lib/alias-hydrate.js +175 -0
  20. package/dist/lib/alias-store.d.ts +1 -0
  21. package/dist/lib/alias-store.js +28 -1
  22. package/dist/lib/auth.js +4 -2
  23. package/dist/lib/chat-endpoint-formatters.d.ts +74 -0
  24. package/dist/lib/chat-endpoint-formatters.js +154 -0
  25. package/dist/lib/chat-endpoint-templates.d.ts +35 -0
  26. package/dist/lib/chat-endpoint-templates.js +210 -0
  27. package/dist/lib/command-helpers.d.ts +18 -0
  28. package/dist/lib/command-helpers.js +105 -3
  29. package/dist/lib/docs.js +641 -17
  30. package/dist/lib/modality.d.ts +42 -0
  31. package/dist/lib/modality.js +192 -0
  32. package/dist/lib/output.d.ts +41 -0
  33. package/dist/lib/output.js +453 -19
  34. package/dist/lib/paths.d.ts +1 -0
  35. package/dist/lib/paths.js +3 -0
  36. package/dist/lib/skill-content.d.ts +18 -0
  37. package/dist/lib/skill-content.js +223 -12
  38. package/dist/lib/types.d.ts +15 -0
  39. 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
+ }
@@ -17,6 +17,7 @@ export declare const ALIAS_PREFIX: {
17
17
  readonly job: "j";
18
18
  readonly ask: "a";
19
19
  readonly askRound: "r";
20
+ readonly chatEndpoint: "ep";
20
21
  };
21
22
  /**
22
23
  * Save aliases for a list of IDs under the given prefix.
@@ -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 a list command first to generate aliases.`);
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 error can't verify, assume ok
28
- console.error("Warning: Could not verify token (network error). Proceeding anyway.");
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[];