@letta-ai/letta-code 0.27.4 → 0.27.5

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.
@@ -0,0 +1,208 @@
1
+ # Extension provider recipes
2
+
3
+ Use provider extensions when the user wants Letta Code local agents to use a model provider that is not built into `/connect` and `/model`.
4
+
5
+ **Important scope:** custom provider extensions are for **local agents only**. They register local provider metadata used by the local backend/TUI/desktop listener. They do not add providers for Constellation/cloud agents.
6
+
7
+ For multi-capability extensions that combine a provider with commands, tools, UI, or state, also read `architecture.md`.
8
+
9
+ ## Contents
10
+
11
+ - When to use
12
+ - Surface behavior and limitations
13
+ - Capability guard
14
+ - Basic API-key provider
15
+ - Config fields
16
+ - Dynamic model discovery
17
+ - Connect behavior
18
+ - Review checklist
19
+
20
+ ## When to use
21
+
22
+ Use a provider extension when:
23
+
24
+ - the provider API is supported by the local pi-ai adapter stack; OpenAI-compatible providers usually use `api: "openai-completions"` or `api: "openai-responses"`
25
+ - the user wants the provider to appear in local `/connect` / desktop Connect model providers
26
+ - local `/model` should show static or dynamically listed models from that provider
27
+ - local turns should resolve model handles like `kilo/kilo-code`
28
+
29
+ Do **not** use a provider extension when the user is asking to configure a Constellation/cloud provider. For cloud agents, use the product's normal provider configuration path instead.
30
+
31
+ ## Surface behavior and limitations
32
+
33
+ - TUI/headless local agents can load provider extensions along with other extension capabilities.
34
+ - The desktop listener loads provider extensions so desktop's local provider UI can list and connect them.
35
+ - Listener provider loading is provider-only. Do not rely on commands, tools, UI, events, or `letta.client` while registering a provider.
36
+ - Provider extensions should use local data, environment variables, local provider connections, and direct `fetch`/Node APIs. They should not use Constellation-specific APIs to make a local provider work.
37
+
38
+ ## Capability guard
39
+
40
+ Always guard provider registration:
41
+
42
+ ```ts
43
+ export default function activate(letta) {
44
+ if (!letta.capabilities.providers) return;
45
+
46
+ return letta.providers.register("kilo", {
47
+ // provider config
48
+ });
49
+ }
50
+ ```
51
+
52
+ `letta.registerProvider(id, config)` is also supported, but prefer `letta.providers.register(...)` so the capability being used is explicit.
53
+
54
+ ## Basic API-key provider
55
+
56
+ ```ts
57
+ // ~/.letta/extensions/kilo.ts
58
+ export default function activate(letta) {
59
+ if (!letta.capabilities.providers) return;
60
+
61
+ return letta.providers.register("kilo", {
62
+ name: "Kilo",
63
+ description: "Connect to Kilo's OpenAI-compatible API",
64
+ api: "openai-completions",
65
+ baseUrl: "https://api.kilo.example/v1",
66
+ apiKey: "KILO_API_KEY",
67
+ models: [
68
+ {
69
+ id: "kilo-code",
70
+ name: "Kilo Code",
71
+ reasoning: true,
72
+ input: ["text"],
73
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
74
+ contextWindow: 128000,
75
+ maxTokens: 8192,
76
+ },
77
+ ],
78
+ connect: {
79
+ fields: [
80
+ { key: "apiKey", label: "Kilo API Key", secret: true },
81
+ ],
82
+ },
83
+ });
84
+ }
85
+ ```
86
+
87
+ After `/reload`, this provider appears in local `/connect` and in the desktop local provider page. The model handle is `<provider-id>/<model-id>`, for example `kilo/kilo-code`.
88
+
89
+ ## Config fields
90
+
91
+ Common provider config fields:
92
+
93
+ ```ts
94
+ {
95
+ name?: string;
96
+ description?: string;
97
+ api?: string; // common: "openai-completions", "openai-responses", "anthropic-messages", "bedrock-converse-stream"
98
+ baseUrl?: string;
99
+ apiKey?: string; // env var name to resolve, e.g. "KILO_API_KEY"
100
+ headers?: Record<string, string>; // values resolve through process.env when present
101
+ authHeader?: boolean; // add Authorization: Bearer <apiKey>
102
+ models?: Array<model>;
103
+ listModels?: (connection) => Promise<Array<model>> | Array<model>;
104
+ connect?: boolean | { fields?: Array<field> };
105
+ }
106
+ ```
107
+
108
+ Check `src/backend/dev/pi-provider-extension-types.ts` and pi-ai model types before documenting or using an uncommon `api` value.
109
+
110
+ Model entries must include useful local runtime metadata:
111
+
112
+ ```ts
113
+ {
114
+ id: "model-id-without-provider-prefix",
115
+ name: "Display Name",
116
+ api?: "openai-completions",
117
+ reasoning: false,
118
+ input: ["text"], // or ["text", "image"]
119
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
120
+ contextWindow: 128000,
121
+ maxTokens: 8192,
122
+ headers?: {},
123
+ }
124
+ ```
125
+
126
+ Do not set `model.baseUrl` when all models use the provider-level URL. Model-level `baseUrl` overrides the connected provider base URL, so `/connect` base URL overrides will be ignored. Use it only when a specific model intentionally needs a different endpoint.
127
+
128
+ ## Dynamic model discovery
129
+
130
+ Use `listModels(connection)` when the provider exposes a models endpoint or the model list depends on connected credentials:
131
+
132
+ ```ts
133
+ export default function activate(letta) {
134
+ if (!letta.capabilities.providers) return;
135
+
136
+ return letta.providers.register("kilo", {
137
+ name: "Kilo",
138
+ api: "openai-completions",
139
+ baseUrl: "https://api.kilo.example/v1",
140
+ apiKey: "KILO_API_KEY",
141
+ async listModels(connection) {
142
+ const response = await fetch(`${connection.baseUrl}/models`, {
143
+ headers: {
144
+ Authorization: `Bearer ${connection.apiKey}`,
145
+ ...connection.headers,
146
+ },
147
+ });
148
+ if (!response.ok) {
149
+ throw new Error(`Kilo model list failed: ${response.status}`);
150
+ }
151
+ const body = await response.json();
152
+ return body.data.map((model) => ({
153
+ id: model.id,
154
+ name: model.id,
155
+ reasoning: false,
156
+ input: ["text"],
157
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
158
+ contextWindow: 128000,
159
+ maxTokens: 8192,
160
+ }));
161
+ },
162
+ });
163
+ }
164
+ ```
165
+
166
+ `connection` contains the provider id/name plus resolved local connection details:
167
+
168
+ ```ts
169
+ {
170
+ id: string;
171
+ providerName: string;
172
+ baseUrl?: string;
173
+ apiKey?: string;
174
+ headers?: Record<string, string>;
175
+ }
176
+ ```
177
+
178
+ Keep dynamic listing lightweight and fail with short actionable errors. If listing is flaky, provide a static `models` fallback instead.
179
+
180
+ ## Connect behavior
181
+
182
+ - `connect: undefined` or `connect: true` uses default API key + base URL fields.
183
+ - `connect: { fields: [...] }` customizes local `/connect` / desktop fields.
184
+ - `connect: false` hides the provider from `/connect`; use only when credentials come entirely from environment variables or local code.
185
+ - `apiKey: "ENV_VAR_NAME"` resolves from `process.env.ENV_VAR_NAME` when available. Do not hardcode real secrets.
186
+ - Custom fields are currently required. `placeholder` is display-only, not a default value.
187
+ - If the provider has a normal fixed `baseUrl`, set provider-level `baseUrl` and omit `baseUrl` from `connect.fields`; the local runtime will use the provider-level URL after the API key is saved.
188
+ - Include `{ key: "baseUrl", ... }` only when the user must enter or override the endpoint during connect.
189
+
190
+ Default custom fields use these keys when possible because the local provider store understands them:
191
+
192
+ ```ts
193
+ { key: "apiKey", label: "API Key", secret: true }
194
+ { key: "baseUrl", label: "Base URL" }
195
+ ```
196
+
197
+ ## Review checklist
198
+
199
+ - The extension says or implies the provider is local-agent only when reporting back to the user.
200
+ - `letta.capabilities.providers` is checked before registration.
201
+ - Provider id is stable, lowercase, and suitable as the model-handle prefix.
202
+ - Model ids are unprefixed; Letta Code exposes them as `<provider-id>/<model-id>`.
203
+ - `contextWindow`, `maxTokens`, `input`, and `reasoning` metadata are accurate enough for local selection and context display.
204
+ - Model-level `baseUrl` is omitted unless the model intentionally needs a different endpoint from the provider/connection.
205
+ - `connect.fields` contains only fields the user must type; placeholders are not treated as defaults.
206
+ - Secrets are read from environment/local provider connections, not hardcoded.
207
+ - Provider registration does not depend on commands/tools/UI/events or `letta.client`.
208
+ - The user is told to run `/reload`, then configure the provider through local `/connect` or desktop Connect model providers.
@@ -4,6 +4,13 @@ Use tools when the agent/model should call a local capability autonomously.
4
4
 
5
5
  For tools that are part of a larger extension with commands, UI, local state, or events, also read `architecture.md`.
6
6
 
7
+ ## Contents
8
+
9
+ - Defaults
10
+ - Read-only shell tool
11
+ - Tool with arguments
12
+ - Mutating or risky tool
13
+
7
14
  ## Defaults
8
15
 
9
16
  - Name: lowercase/underscore tool name, e.g. `branch_summary`.