@matterailab/orbcode 0.1.13 → 0.2.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 +58 -13
- package/dist/api/aiSdkClient.js +260 -0
- package/dist/api/client.js +2 -1
- package/dist/api/llmClient.js +19 -0
- package/dist/api/models.js +106 -0
- package/dist/api/provider.js +16 -0
- package/dist/auth/auth.js +2 -2
- package/dist/config/settings.js +10 -10
- package/dist/core/agent.js +15 -3
- package/dist/core/hooks.js +6 -6
- package/dist/headless.js +19 -4
- package/dist/index.js +2 -2
- package/dist/ui/App.js +1 -1
- package/dist/ui/LoginView.js +1 -1
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -130,10 +130,10 @@ Fallbacks & overrides:
|
|
|
130
130
|
- **settings.json key**: set `apiKey` in `~/.orbcode/settings.json` (or a
|
|
131
131
|
project's `.orbcode/settings.json`) to skip login. The login screen itself is
|
|
132
132
|
browser-redirect only.
|
|
133
|
-
- **Env token**: set `
|
|
133
|
+
- **Env token**: set `MATTERAI_TOKEN` to skip login entirely (takes precedence
|
|
134
134
|
over everything).
|
|
135
|
-
- **Dev endpoints**: `
|
|
136
|
-
and `
|
|
135
|
+
- **Dev endpoints**: `MATTERAI_BACKEND_URL` (default `https://api.matterai.so`)
|
|
136
|
+
and `MATTERAI_APP_URL` (default `https://app.matterai.so`) override where the
|
|
137
137
|
device flow points — useful against a local backend/webapp.
|
|
138
138
|
- Tokens are MatterAI JWTs. A token whose payload has `env: "development"`
|
|
139
139
|
automatically routes API calls to `http://localhost:3000`, matching the
|
|
@@ -157,6 +157,51 @@ The two Axon models are built in; `/model` opens a scroll-and-select picker
|
|
|
157
157
|
image input. Cost comes from the API's usage chunks (`is_byok`-aware) and is
|
|
158
158
|
shown in the status bar.
|
|
159
159
|
|
|
160
|
+
### Other providers (Anthropic, OpenAI-compatible)
|
|
161
|
+
|
|
162
|
+
The Axon models go through the MatterAI gateway as before. A `customModels`
|
|
163
|
+
entry that sets a `provider` is instead served through the
|
|
164
|
+
[Vercel AI SDK](https://sdk.vercel.ai), reusing the same agent loop, tools, and
|
|
165
|
+
approvals — auth is the provider's own key (env var or `apiKey`), not the
|
|
166
|
+
MatterAI login.
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"model": "claude-opus-4-8",
|
|
171
|
+
"customModels": [
|
|
172
|
+
{
|
|
173
|
+
"id": "claude-opus-4-8",
|
|
174
|
+
"name": "Claude Opus 4.8",
|
|
175
|
+
"provider": "anthropic",
|
|
176
|
+
"contextWindow": 1000000,
|
|
177
|
+
"maxOutputTokens": 64000,
|
|
178
|
+
"inputPrice": 0.000005,
|
|
179
|
+
"outputPrice": 0.000025,
|
|
180
|
+
"effort": "high"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"id": "some-model",
|
|
184
|
+
"provider": "openai-compatible",
|
|
185
|
+
"baseUrl": "https://api.other-host.com/v1"
|
|
186
|
+
}
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
- `provider: "anthropic"` → native `/v1/messages` (`@ai-sdk/anthropic`). Key from
|
|
192
|
+
`ANTHROPIC_API_KEY` (or `apiKey` on the entry). Adaptive thinking + reasoning
|
|
193
|
+
streaming are on by default; `effort` (`low`…`max`) tunes depth; prompt
|
|
194
|
+
caching breakpoints are set on the system prompt and conversation prefix
|
|
195
|
+
automatically. Thinking blocks are preserved across turns (stored with the
|
|
196
|
+
session and replayed with their signatures), so interleaved thinking with
|
|
197
|
+
tool use round-trips correctly. Set `"reasoning": false` to disable thinking
|
|
198
|
+
(e.g. for models that don't support `effort`).
|
|
199
|
+
- `provider: "openai-compatible"` → any OpenAI-compatible endpoint; requires
|
|
200
|
+
`baseUrl`. Key from `apiKey` on the entry.
|
|
201
|
+
|
|
202
|
+
Anything without a `provider` (or `provider: "matterai"`) keeps using the
|
|
203
|
+
MatterAI gateway untouched.
|
|
204
|
+
|
|
160
205
|
## The TUI
|
|
161
206
|
|
|
162
207
|
```
|
|
@@ -301,13 +346,13 @@ and `--resume <id>`.
|
|
|
301
346
|
|
|
302
347
|
| env var | effect |
|
|
303
348
|
| --------------------- | ----------------------------------------------------------------- |
|
|
304
|
-
| `
|
|
305
|
-
| `
|
|
306
|
-
| `
|
|
307
|
-
| `
|
|
308
|
-
| `
|
|
309
|
-
| `
|
|
310
|
-
| `
|
|
349
|
+
| `MATTERAI_TOKEN` | auth token (overrides everything) |
|
|
350
|
+
| `MATTERAI_API_KEY` | same as `apiKey` in settings.json |
|
|
351
|
+
| `MATTERAI_BASE_URL` | same as `baseUrl` in settings.json |
|
|
352
|
+
| `MATTERAI_MODEL` | model override (what `--model` sets internally) |
|
|
353
|
+
| `MATTERAI_CONFIG_DIR` | config directory (default `~/.orbcode`) |
|
|
354
|
+
| `MATTERAI_BACKEND_URL` | device-auth backend (default `https://api.matterai.so`) |
|
|
355
|
+
| `MATTERAI_APP_URL` | webapp for the authorize page (default `https://app.matterai.so`) |
|
|
311
356
|
|
|
312
357
|
`autoApproveEdits` / `autoApproveSafeCommands` set the session defaults for the
|
|
313
358
|
approval prompts (dangerous commands still always prompt); shift+tab cycles
|
|
@@ -320,7 +365,7 @@ them to **block** dangerous actions, **auto-approve** trusted ones, **rewrite**
|
|
|
320
365
|
tool inputs, **inject context** into the model, **format code** after edits,
|
|
321
366
|
**notify** you, or **keep the agent working** until a condition is met. They use
|
|
322
367
|
the **same contract as Claude Code's hooks**, so scripts written for it work
|
|
323
|
-
here (just use `$
|
|
368
|
+
here (just use `$MATTERAI_PROJECT_DIR` and OrbCode's tool names).
|
|
324
369
|
|
|
325
370
|
> 📖 **This is the overview. The complete, example-driven reference —
|
|
326
371
|
> per-event input/output, a copy-paste cookbook, debugging, and security — is in
|
|
@@ -543,8 +588,8 @@ node test-device-auth.mjs # device-auth polling flow against a local mock
|
|
|
543
588
|
command runs `dist/`, and the version is read from `package.json` at runtime.
|
|
544
589
|
- **Stale link after the rename** — `npm unlink -g orbitalcode && npm link`.
|
|
545
590
|
- **Login times out** — the device code lives 10 minutes; press Enter to retry,
|
|
546
|
-
or paste a token manually. Against a local stack, set `
|
|
547
|
-
and `
|
|
591
|
+
or paste a token manually. Against a local stack, set `MATTERAI_BACKEND_URL`
|
|
592
|
+
and `MATTERAI_APP_URL`.
|
|
548
593
|
- **401 on chat** — token expired: `/logout` then `/login`.
|
|
549
594
|
- **Keyboard input does nothing** — OrbCode needs a real TTY (raw mode); it
|
|
550
595
|
won't accept piped stdin. Use `-p` for non-interactive runs.
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
2
|
+
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
3
|
+
import { jsonSchema, streamText, tool, } from "ai";
|
|
4
|
+
import { REASONING_DETAILS_FIELD } from "./llmClient.js";
|
|
5
|
+
/**
|
|
6
|
+
* LLM transport backed by the Vercel AI SDK. Serves any non-MatterAI provider
|
|
7
|
+
* (Anthropic native `/v1/messages`, or any OpenAI-compatible endpoint) while
|
|
8
|
+
* speaking the same OpenAI-shaped message/tool contract the agent loop uses —
|
|
9
|
+
* translation happens entirely at this boundary. Auth is the provider's own
|
|
10
|
+
* key (env var or settings), never the MatterAI token.
|
|
11
|
+
*/
|
|
12
|
+
export class AiSdkClient {
|
|
13
|
+
options;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.options = options;
|
|
16
|
+
}
|
|
17
|
+
async *createMessage(systemPrompt, messages, tools, abortSignal) {
|
|
18
|
+
const model = this.options.model;
|
|
19
|
+
const isAnthropic = model.provider === "anthropic";
|
|
20
|
+
// Replay stored thinking blocks only on Anthropic, where they round-trip
|
|
21
|
+
// with their signatures; other providers ignore the side-channel.
|
|
22
|
+
const aiMessages = toModelMessages(messages, isAnthropic);
|
|
23
|
+
if (isAnthropic)
|
|
24
|
+
applyCacheBreakpoint(aiMessages);
|
|
25
|
+
const aiTools = toAiTools(tools);
|
|
26
|
+
const hasTools = Object.keys(aiTools).length > 0;
|
|
27
|
+
const result = streamText({
|
|
28
|
+
model: this.resolveModel(),
|
|
29
|
+
system: this.buildSystem(systemPrompt, isAnthropic),
|
|
30
|
+
messages: aiMessages,
|
|
31
|
+
...(hasTools ? { tools: aiTools, toolChoice: "auto" } : {}),
|
|
32
|
+
maxOutputTokens: model.maxOutputTokens,
|
|
33
|
+
// No `temperature`: the current Claude models reject sampling params.
|
|
34
|
+
abortSignal,
|
|
35
|
+
providerOptions: this.providerOptions(isAnthropic),
|
|
36
|
+
});
|
|
37
|
+
for await (const part of result.fullStream) {
|
|
38
|
+
switch (part.type) {
|
|
39
|
+
case "text-delta":
|
|
40
|
+
if (part.text)
|
|
41
|
+
yield { type: "text", text: part.text };
|
|
42
|
+
break;
|
|
43
|
+
case "reasoning-delta":
|
|
44
|
+
if (part.text)
|
|
45
|
+
yield { type: "reasoning", text: part.text };
|
|
46
|
+
break;
|
|
47
|
+
case "tool-call":
|
|
48
|
+
yield {
|
|
49
|
+
type: "native_tool_calls",
|
|
50
|
+
toolCalls: [
|
|
51
|
+
{
|
|
52
|
+
id: part.toolCallId,
|
|
53
|
+
type: "function",
|
|
54
|
+
function: {
|
|
55
|
+
name: part.toolName,
|
|
56
|
+
arguments: JSON.stringify(part.input ?? {}),
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
break;
|
|
62
|
+
case "finish":
|
|
63
|
+
yield this.usageChunk(part.totalUsage);
|
|
64
|
+
break;
|
|
65
|
+
case "error":
|
|
66
|
+
throw asError(part.error);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Capture this turn's thinking blocks (with their provider signatures) so
|
|
70
|
+
// the agent can stash them on the assistant message and replay them next
|
|
71
|
+
// turn — required for interleaved thinking + tool use on the same model.
|
|
72
|
+
const reasoning = collectReasoningParts((await result.response).messages);
|
|
73
|
+
if (reasoning.length > 0) {
|
|
74
|
+
yield { type: "reasoning_details", details: reasoning };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/** Build the provider model handle for this request. */
|
|
78
|
+
resolveModel() {
|
|
79
|
+
const { provider, id, baseUrl, apiKey } = this.options.model;
|
|
80
|
+
switch (provider) {
|
|
81
|
+
case "anthropic":
|
|
82
|
+
// createAnthropic reads ANTHROPIC_API_KEY from the env when apiKey is absent.
|
|
83
|
+
return createAnthropic(apiKey ? { apiKey } : {})(id);
|
|
84
|
+
default: {
|
|
85
|
+
// Any OpenAI-compatible endpoint (OpenRouter, Together, Groq, a local
|
|
86
|
+
// server, even api.openai.com/v1). Adding a native provider package
|
|
87
|
+
// (e.g. @ai-sdk/google) is a new `case` here.
|
|
88
|
+
if (!baseUrl) {
|
|
89
|
+
throw new Error(`Model "${id}" uses provider "${provider}" but no baseUrl is set. Add a "baseUrl" to its customModels entry in settings.json.`);
|
|
90
|
+
}
|
|
91
|
+
return createOpenAICompatible({ name: provider ?? "openai-compatible", baseURL: baseUrl, apiKey })(id);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Cache the system prompt (and, on Anthropic, the tool list with it). */
|
|
96
|
+
buildSystem(systemPrompt, isAnthropic) {
|
|
97
|
+
if (!isAnthropic)
|
|
98
|
+
return systemPrompt;
|
|
99
|
+
return {
|
|
100
|
+
role: "system",
|
|
101
|
+
content: systemPrompt,
|
|
102
|
+
providerOptions: { anthropic: { cacheControl: { type: "ephemeral" } } },
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/** Adaptive thinking + effort + reasoning replay for Anthropic; nothing otherwise. */
|
|
106
|
+
providerOptions(isAnthropic) {
|
|
107
|
+
if (!isAnthropic)
|
|
108
|
+
return undefined;
|
|
109
|
+
const model = this.options.model;
|
|
110
|
+
if (model.reasoning === false)
|
|
111
|
+
return undefined;
|
|
112
|
+
return {
|
|
113
|
+
anthropic: {
|
|
114
|
+
thinking: { type: "adaptive", display: "summarized" },
|
|
115
|
+
effort: model.effort ?? "high",
|
|
116
|
+
sendReasoning: true,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
usageChunk(usage) {
|
|
121
|
+
const model = this.options.model;
|
|
122
|
+
const inputTokens = usage.inputTokens ?? 0;
|
|
123
|
+
const outputTokens = usage.outputTokens ?? 0;
|
|
124
|
+
return {
|
|
125
|
+
type: "usage",
|
|
126
|
+
inputTokens,
|
|
127
|
+
outputTokens,
|
|
128
|
+
cacheReadTokens: usage.inputTokenDetails?.cacheReadTokens,
|
|
129
|
+
reasoningTokens: usage.outputTokenDetails?.reasoningTokens,
|
|
130
|
+
totalCost: model.free ? 0 : inputTokens * model.inputPrice + outputTokens * model.outputPrice,
|
|
131
|
+
inferenceProvider: model.provider,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Translate OpenAI-shaped function tools into the AI SDK's client-side tool set
|
|
136
|
+
* (no `execute` — the agent loop runs the tool and feeds the result back). */
|
|
137
|
+
function toAiTools(tools) {
|
|
138
|
+
const set = {};
|
|
139
|
+
for (const t of tools) {
|
|
140
|
+
if (t.type !== "function")
|
|
141
|
+
continue;
|
|
142
|
+
set[t.function.name] = tool({
|
|
143
|
+
description: t.function.description,
|
|
144
|
+
inputSchema: jsonSchema(t.function.parameters ?? { type: "object", properties: {} }),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return set;
|
|
148
|
+
}
|
|
149
|
+
/** Translate the OpenAI conversation history into AI SDK `ModelMessage`s.
|
|
150
|
+
* When `includeReasoning`, stored thinking blocks are prepended to each
|
|
151
|
+
* assistant turn (first, ahead of text/tool-call parts) for same-model replay. */
|
|
152
|
+
function toModelMessages(messages, includeReasoning) {
|
|
153
|
+
// tool_result parts need the tool name, which OpenAI only carries on the
|
|
154
|
+
// originating assistant tool_call — index it first.
|
|
155
|
+
const toolNameById = new Map();
|
|
156
|
+
for (const message of messages) {
|
|
157
|
+
if (message.role === "assistant" && message.tool_calls) {
|
|
158
|
+
for (const call of message.tool_calls) {
|
|
159
|
+
if (call.type === "function")
|
|
160
|
+
toolNameById.set(call.id, call.function.name);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const out = [];
|
|
165
|
+
for (const message of messages) {
|
|
166
|
+
switch (message.role) {
|
|
167
|
+
case "user":
|
|
168
|
+
out.push({ role: "user", content: contentToText(message.content) });
|
|
169
|
+
break;
|
|
170
|
+
case "assistant": {
|
|
171
|
+
const parts = [];
|
|
172
|
+
if (includeReasoning)
|
|
173
|
+
parts.push(...storedReasoningParts(message));
|
|
174
|
+
const text = contentToText(message.content);
|
|
175
|
+
if (text)
|
|
176
|
+
parts.push({ type: "text", text });
|
|
177
|
+
for (const call of message.tool_calls ?? []) {
|
|
178
|
+
if (call.type !== "function")
|
|
179
|
+
continue;
|
|
180
|
+
parts.push({
|
|
181
|
+
type: "tool-call",
|
|
182
|
+
toolCallId: call.id,
|
|
183
|
+
toolName: call.function.name,
|
|
184
|
+
input: safeParseJson(call.function.arguments),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
out.push({ role: "assistant", content: parts.length > 0 ? parts : "" });
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
case "tool": {
|
|
191
|
+
const result = {
|
|
192
|
+
type: "tool-result",
|
|
193
|
+
toolCallId: message.tool_call_id,
|
|
194
|
+
toolName: toolNameById.get(message.tool_call_id) ?? "tool",
|
|
195
|
+
output: { type: "text", value: contentToText(message.content) },
|
|
196
|
+
};
|
|
197
|
+
out.push({ role: "tool", content: [result] });
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
// "system"/"developer"/"function" roles don't appear in OrbCode history
|
|
201
|
+
// (the system prompt is passed separately) — ignore defensively.
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return out;
|
|
205
|
+
}
|
|
206
|
+
/** Pull the reasoning parts out of the assistant message(s) the SDK assembled
|
|
207
|
+
* for this turn — these carry the provider signature needed to replay them. */
|
|
208
|
+
function collectReasoningParts(messages) {
|
|
209
|
+
const parts = [];
|
|
210
|
+
for (const message of messages) {
|
|
211
|
+
if (message.role !== "assistant" || !Array.isArray(message.content))
|
|
212
|
+
continue;
|
|
213
|
+
for (const part of message.content) {
|
|
214
|
+
if (part.type === "reasoning")
|
|
215
|
+
parts.push(part);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return parts;
|
|
219
|
+
}
|
|
220
|
+
/** Read back the reasoning parts the agent stashed on a prior assistant turn. */
|
|
221
|
+
function storedReasoningParts(message) {
|
|
222
|
+
const details = message[REASONING_DETAILS_FIELD];
|
|
223
|
+
return Array.isArray(details) ? details : [];
|
|
224
|
+
}
|
|
225
|
+
/** Tag the last message so Anthropic caches the conversation prefix up to here. */
|
|
226
|
+
function applyCacheBreakpoint(messages) {
|
|
227
|
+
const last = messages[messages.length - 1];
|
|
228
|
+
if (!last)
|
|
229
|
+
return;
|
|
230
|
+
last.providerOptions = {
|
|
231
|
+
...last.providerOptions,
|
|
232
|
+
anthropic: { ...last.providerOptions?.anthropic, cacheControl: { type: "ephemeral" } },
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/** Flatten OpenAI string-or-parts content down to plain text. */
|
|
236
|
+
function contentToText(content) {
|
|
237
|
+
if (typeof content === "string")
|
|
238
|
+
return content;
|
|
239
|
+
if (Array.isArray(content)) {
|
|
240
|
+
return content
|
|
241
|
+
.map((part) => part && typeof part === "object" && "text" in part ? String(part.text ?? "") : "")
|
|
242
|
+
.join("");
|
|
243
|
+
}
|
|
244
|
+
return "";
|
|
245
|
+
}
|
|
246
|
+
function safeParseJson(raw) {
|
|
247
|
+
if (!raw)
|
|
248
|
+
return {};
|
|
249
|
+
try {
|
|
250
|
+
return JSON.parse(raw);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
return {};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
function asError(error) {
|
|
257
|
+
if (error instanceof Error)
|
|
258
|
+
return error;
|
|
259
|
+
return new Error(typeof error === "string" ? error : JSON.stringify(error));
|
|
260
|
+
}
|
package/dist/api/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import OpenAI from "openai";
|
|
2
2
|
import { API_GATEWAY_PATH, getUrlFromToken } from "../auth/auth.js";
|
|
3
3
|
import { DEFAULT_HEADERS, X_AXONCODE_TASKID, X_AXON_REPO, X_ORGANIZATIONID } from "./headers.js";
|
|
4
|
+
import { stripReasoningDetails } from "./llmClient.js";
|
|
4
5
|
import { getModel } from "./models.js";
|
|
5
6
|
export class AxonClient {
|
|
6
7
|
client;
|
|
@@ -26,7 +27,7 @@ export class AxonClient {
|
|
|
26
27
|
const requestOptions = {
|
|
27
28
|
model: model.id,
|
|
28
29
|
temperature: 0.2,
|
|
29
|
-
messages: [{ role: "system", content: systemPrompt }, ...messages],
|
|
30
|
+
messages: [{ role: "system", content: systemPrompt }, ...stripReasoningDetails(messages)],
|
|
30
31
|
stream: true,
|
|
31
32
|
stream_options: { include_usage: true },
|
|
32
33
|
max_tokens: model.maxOutputTokens,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-standard field stashed on a persisted assistant message holding opaque
|
|
3
|
+
* reasoning blocks (with provider signatures) for same-model replay. Only the
|
|
4
|
+
* AI SDK client reads/writes it; it is stripped before any OpenAI request.
|
|
5
|
+
*/
|
|
6
|
+
export const REASONING_DETAILS_FIELD = "_reasoningDetails";
|
|
7
|
+
/** Drop the reasoning side-channel from assistant messages bound for an OpenAI
|
|
8
|
+
* `/chat/completions` request (it isn't a valid input field there). */
|
|
9
|
+
export function stripReasoningDetails(messages) {
|
|
10
|
+
return messages.map((message) => {
|
|
11
|
+
if (message.role !== "assistant")
|
|
12
|
+
return message;
|
|
13
|
+
const record = message;
|
|
14
|
+
if (!(REASONING_DETAILS_FIELD in record))
|
|
15
|
+
return message;
|
|
16
|
+
const { [REASONING_DETAILS_FIELD]: _omit, ...rest } = record;
|
|
17
|
+
return rest;
|
|
18
|
+
});
|
|
19
|
+
}
|
package/dist/api/models.js
CHANGED
|
@@ -1,4 +1,94 @@
|
|
|
1
|
+
/** Providers served by the built-in MatterAI gateway rather than the AI SDK. */
|
|
2
|
+
const MATTERAI_PROVIDERS = new Set(["matterai", "axon"]);
|
|
3
|
+
/** True when the model should be served via the Vercel AI SDK transport. */
|
|
4
|
+
export function usesAiSdk(model) {
|
|
5
|
+
return Boolean(model.provider) && !MATTERAI_PROVIDERS.has(model.provider);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Well-known Claude models, served natively via the Anthropic provider (AI SDK,
|
|
9
|
+
* `/v1/messages`). Built in so `--model claude-…` works without a settings.json
|
|
10
|
+
* entry; auth is `ANTHROPIC_API_KEY` (or a per-model `apiKey`), never MatterAI.
|
|
11
|
+
* Adaptive thinking + effort are on by default (see AiSdkClient); Haiku 4.5 sets
|
|
12
|
+
* `reasoning: false` because it rejects the `effort` parameter.
|
|
13
|
+
*/
|
|
14
|
+
export const ANTHROPIC_MODELS = {
|
|
15
|
+
"claude-opus-4-8": {
|
|
16
|
+
id: "claude-opus-4-8",
|
|
17
|
+
name: "Claude Opus 4.8",
|
|
18
|
+
description: "Anthropic's most capable Opus model — long-horizon agentic work, knowledge work, and coding.",
|
|
19
|
+
contextWindow: 1_000_000,
|
|
20
|
+
maxOutputTokens: 64000,
|
|
21
|
+
supportsImages: true,
|
|
22
|
+
inputPrice: 0.000005,
|
|
23
|
+
outputPrice: 0.000025,
|
|
24
|
+
free: false,
|
|
25
|
+
provider: "anthropic",
|
|
26
|
+
},
|
|
27
|
+
"claude-opus-4-7": {
|
|
28
|
+
id: "claude-opus-4-7",
|
|
29
|
+
name: "Claude Opus 4.7",
|
|
30
|
+
description: "Previous-generation Opus — highly autonomous, strong on agentic, vision, and memory tasks.",
|
|
31
|
+
contextWindow: 1_000_000,
|
|
32
|
+
maxOutputTokens: 64000,
|
|
33
|
+
supportsImages: true,
|
|
34
|
+
inputPrice: 0.000005,
|
|
35
|
+
outputPrice: 0.000025,
|
|
36
|
+
free: false,
|
|
37
|
+
provider: "anthropic",
|
|
38
|
+
},
|
|
39
|
+
"claude-opus-4-6": {
|
|
40
|
+
id: "claude-opus-4-6",
|
|
41
|
+
name: "Claude Opus 4.6",
|
|
42
|
+
description: "Older Opus with adaptive thinking; 1M context.",
|
|
43
|
+
contextWindow: 1_000_000,
|
|
44
|
+
maxOutputTokens: 64000,
|
|
45
|
+
supportsImages: true,
|
|
46
|
+
inputPrice: 0.000005,
|
|
47
|
+
outputPrice: 0.000025,
|
|
48
|
+
free: false,
|
|
49
|
+
provider: "anthropic",
|
|
50
|
+
},
|
|
51
|
+
"claude-sonnet-4-6": {
|
|
52
|
+
id: "claude-sonnet-4-6",
|
|
53
|
+
name: "Claude Sonnet 4.6",
|
|
54
|
+
description: "Anthropic's best balance of speed and intelligence; adaptive thinking, 1M context.",
|
|
55
|
+
contextWindow: 1_000_000,
|
|
56
|
+
maxOutputTokens: 64000,
|
|
57
|
+
supportsImages: true,
|
|
58
|
+
inputPrice: 0.000003,
|
|
59
|
+
outputPrice: 0.000015,
|
|
60
|
+
free: false,
|
|
61
|
+
provider: "anthropic",
|
|
62
|
+
},
|
|
63
|
+
"claude-haiku-4-5": {
|
|
64
|
+
id: "claude-haiku-4-5",
|
|
65
|
+
name: "Claude Haiku 4.5",
|
|
66
|
+
description: "Fastest, most cost-effective Claude model for simple, latency-sensitive tasks.",
|
|
67
|
+
contextWindow: 200_000,
|
|
68
|
+
maxOutputTokens: 64000,
|
|
69
|
+
supportsImages: true,
|
|
70
|
+
inputPrice: 0.000001,
|
|
71
|
+
outputPrice: 0.000005,
|
|
72
|
+
free: false,
|
|
73
|
+
provider: "anthropic",
|
|
74
|
+
// Haiku 4.5 rejects the `effort` parameter, so don't send thinking/effort.
|
|
75
|
+
reasoning: false,
|
|
76
|
+
},
|
|
77
|
+
"claude-fable-5": {
|
|
78
|
+
id: "claude-fable-5",
|
|
79
|
+
name: "Claude Fable 5",
|
|
80
|
+
description: "Anthropic's most capable widely released model — most demanding reasoning and long-horizon work.",
|
|
81
|
+
contextWindow: 1_000_000,
|
|
82
|
+
maxOutputTokens: 64000,
|
|
83
|
+
supportsImages: true,
|
|
84
|
+
inputPrice: 0.00001,
|
|
85
|
+
outputPrice: 0.00005,
|
|
86
|
+
free: false,
|
|
87
|
+
provider: "anthropic",
|
|
88
|
+
},
|
|
89
|
+
};
|
|
1
90
|
export const AXON_MODELS = {
|
|
91
|
+
...ANTHROPIC_MODELS,
|
|
2
92
|
"axon-code-2-5-mini": {
|
|
3
93
|
id: "axon-code-2-5-mini",
|
|
4
94
|
name: "Axon Code 2.5 Mini (free)",
|
|
@@ -32,6 +122,17 @@ export const AXON_MODELS = {
|
|
|
32
122
|
outputPrice: 0.000009,
|
|
33
123
|
free: false,
|
|
34
124
|
},
|
|
125
|
+
"axon-eido-3-code-mini": {
|
|
126
|
+
id: "axon-eido-3-code-mini",
|
|
127
|
+
name: "Axon Eido 3 Mini",
|
|
128
|
+
description: "Axon Eido 3 Mini is a general purpose super intelligent LLM coding model for low-effort day-to-day tasks",
|
|
129
|
+
contextWindow: 400000,
|
|
130
|
+
maxOutputTokens: 64000,
|
|
131
|
+
supportsImages: true,
|
|
132
|
+
inputPrice: 0.000001,
|
|
133
|
+
outputPrice: 0.000003,
|
|
134
|
+
free: false,
|
|
135
|
+
},
|
|
35
136
|
};
|
|
36
137
|
export const DEFAULT_MODEL_ID = "axon-code-2-5-pro";
|
|
37
138
|
/** Add user-defined models (from settings.json) to the registry. */
|
|
@@ -49,6 +150,11 @@ export function registerCustomModels(models) {
|
|
|
49
150
|
inputPrice: model.inputPrice ?? 0,
|
|
50
151
|
outputPrice: model.outputPrice ?? 0,
|
|
51
152
|
free: (model.inputPrice ?? 0) === 0 && (model.outputPrice ?? 0) === 0,
|
|
153
|
+
provider: model.provider,
|
|
154
|
+
baseUrl: model.baseUrl,
|
|
155
|
+
apiKey: model.apiKey,
|
|
156
|
+
effort: model.effort,
|
|
157
|
+
reasoning: model.reasoning,
|
|
52
158
|
};
|
|
53
159
|
}
|
|
54
160
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AiSdkClient } from "./aiSdkClient.js";
|
|
2
|
+
import { AxonClient } from "./client.js";
|
|
3
|
+
import { getModel, usesAiSdk } from "./models.js";
|
|
4
|
+
/**
|
|
5
|
+
* Pick the transport for a model. MatterAI/Axon models keep using `AxonClient`
|
|
6
|
+
* (OpenAI `/chat/completions` against the gateway, with all its auth/headers/
|
|
7
|
+
* cost handling); anything declaring another `provider` goes through the Vercel
|
|
8
|
+
* AI SDK. Both implement `LLMClient`, so the agent loop is unaffected.
|
|
9
|
+
*/
|
|
10
|
+
export function createLLMClient(options) {
|
|
11
|
+
const model = getModel(options.modelId);
|
|
12
|
+
if (usesAiSdk(model)) {
|
|
13
|
+
return new AiSdkClient({ model });
|
|
14
|
+
}
|
|
15
|
+
return new AxonClient(options);
|
|
16
|
+
}
|
package/dist/auth/auth.js
CHANGED
|
@@ -32,10 +32,10 @@ export function getSignInUrl() {
|
|
|
32
32
|
return `${APP_URL}/authentication/sign-in?loginType=extension&source=cli`;
|
|
33
33
|
}
|
|
34
34
|
function deviceAuthBaseUrl() {
|
|
35
|
-
return process.env.
|
|
35
|
+
return process.env.MATTERAI_BACKEND_URL || DEFAULT_BACKEND_URL;
|
|
36
36
|
}
|
|
37
37
|
function deviceAuthAppUrl() {
|
|
38
|
-
return process.env.
|
|
38
|
+
return process.env.MATTERAI_APP_URL || APP_URL;
|
|
39
39
|
}
|
|
40
40
|
export async function startDeviceAuth() {
|
|
41
41
|
const response = await fetch(`${deviceAuthBaseUrl()}/orbcode/auth/start`, {
|
package/dist/config/settings.js
CHANGED
|
@@ -20,7 +20,7 @@ const SETTINGS_KEYS = [
|
|
|
20
20
|
"env",
|
|
21
21
|
];
|
|
22
22
|
export function getConfigDir() {
|
|
23
|
-
return process.env.
|
|
23
|
+
return process.env.MATTERAI_CONFIG_DIR || path.join(os.homedir(), ".orbcode");
|
|
24
24
|
}
|
|
25
25
|
function getConfigPath() {
|
|
26
26
|
return path.join(getConfigDir(), "config.json");
|
|
@@ -76,7 +76,7 @@ function isProjectHooksTrusted(cwd, hash) {
|
|
|
76
76
|
// stdin is not a TTY (so a stray export in a shell rc file can't silently
|
|
77
77
|
// disable the trust gate for interactive sessions). A non-TTY check keeps
|
|
78
78
|
// the gate meaningful in the TUI while still letting CI opt in.
|
|
79
|
-
if (process.env.
|
|
79
|
+
if (process.env.MATTERAI_TRUST_PROJECT_HOOKS === "1" && !process.stdin.isTTY)
|
|
80
80
|
return true;
|
|
81
81
|
const store = readJson(getTrustStorePath());
|
|
82
82
|
return Boolean(store && store[cwd] === hash);
|
|
@@ -179,23 +179,23 @@ export function loadSettings() {
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
// Environment variables take precedence over all files.
|
|
182
|
-
if (process.env.
|
|
183
|
-
settings.baseUrl = process.env.
|
|
184
|
-
if (process.env.
|
|
185
|
-
settings.apiKey = process.env.
|
|
186
|
-
if (process.env.
|
|
187
|
-
settings.model = process.env.
|
|
182
|
+
if (process.env.MATTERAI_BASE_URL)
|
|
183
|
+
settings.baseUrl = process.env.MATTERAI_BASE_URL;
|
|
184
|
+
if (process.env.MATTERAI_API_KEY)
|
|
185
|
+
settings.apiKey = process.env.MATTERAI_API_KEY;
|
|
186
|
+
if (process.env.MATTERAI_MODEL)
|
|
187
|
+
settings.model = process.env.MATTERAI_MODEL;
|
|
188
188
|
if (!isValidAxonModel(settings.model)) {
|
|
189
189
|
settings.model = DEFAULT_MODEL_ID;
|
|
190
190
|
}
|
|
191
191
|
return settings;
|
|
192
192
|
}
|
|
193
193
|
/**
|
|
194
|
-
* Effective credential:
|
|
194
|
+
* Effective credential: MATTERAI_TOKEN env > apiKey (settings.json / env) >
|
|
195
195
|
* stored login token.
|
|
196
196
|
*/
|
|
197
197
|
export function getAuthToken(settings) {
|
|
198
|
-
return process.env.
|
|
198
|
+
return process.env.MATTERAI_TOKEN || settings.apiKey || settings.token;
|
|
199
199
|
}
|
|
200
200
|
/** Persist app state to config.json. settings.json-only fields are skipped. */
|
|
201
201
|
export function saveSettings(settings) {
|
package/dist/core/agent.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
|
-
import {
|
|
4
|
+
import { REASONING_DETAILS_FIELD } from "../api/llmClient.js";
|
|
5
|
+
import { createLLMClient } from "../api/provider.js";
|
|
5
6
|
import { buildSystemPrompt } from "../prompts/system.js";
|
|
6
7
|
import { describeToolCall, executeTool, getActiveTools, getApprovalKind, } from "../tools/index.js";
|
|
7
8
|
import { walkFiles } from "../tools/executors/listFiles.js";
|
|
@@ -107,7 +108,7 @@ export class Agent {
|
|
|
107
108
|
}
|
|
108
109
|
this.sessionApproveEdits = options.autoApproveEdits;
|
|
109
110
|
this.systemPrompt = buildSystemPrompt(options.cwd);
|
|
110
|
-
this.client =
|
|
111
|
+
this.client = createLLMClient({
|
|
111
112
|
token: options.token,
|
|
112
113
|
modelId: options.modelId,
|
|
113
114
|
taskId: this.taskId,
|
|
@@ -118,7 +119,7 @@ export class Agent {
|
|
|
118
119
|
}
|
|
119
120
|
setModel(modelId) {
|
|
120
121
|
this.options.modelId = modelId;
|
|
121
|
-
this.client =
|
|
122
|
+
this.client = createLLMClient({
|
|
122
123
|
token: this.options.token,
|
|
123
124
|
modelId,
|
|
124
125
|
taskId: this.taskId,
|
|
@@ -448,6 +449,7 @@ User time zone: ${timeZone}, UTC${timeZoneOffsetStr}`;
|
|
|
448
449
|
let assistantText = "";
|
|
449
450
|
let hadReasoning = false;
|
|
450
451
|
let reasoningStart = 0;
|
|
452
|
+
let reasoningDetails;
|
|
451
453
|
const toolCallsByIndex = new Map();
|
|
452
454
|
let nextSyntheticIndex = 10000;
|
|
453
455
|
const stream = this.client.createMessage(this.systemPrompt, this.outgoingMessages(), getActiveTools(), signal);
|
|
@@ -466,6 +468,10 @@ User time zone: ${timeZone}, UTC${timeZoneOffsetStr}`;
|
|
|
466
468
|
}
|
|
467
469
|
onEvent({ type: "reasoning-delta", text: chunk.text });
|
|
468
470
|
break;
|
|
471
|
+
case "reasoning_details":
|
|
472
|
+
// Opaque thinking blocks (with signatures) for next-turn replay.
|
|
473
|
+
reasoningDetails = chunk.details;
|
|
474
|
+
break;
|
|
469
475
|
case "native_tool_calls":
|
|
470
476
|
for (const tc of chunk.toolCalls) {
|
|
471
477
|
const index = tc.index ?? nextSyntheticIndex++;
|
|
@@ -513,6 +519,12 @@ User time zone: ${timeZone}, UTC${timeZoneOffsetStr}`;
|
|
|
513
519
|
function: { name: tc.name, arguments: tc.arguments || "{}" },
|
|
514
520
|
}));
|
|
515
521
|
}
|
|
522
|
+
// Stash reasoning blocks (opaque) so the next turn can replay them. The
|
|
523
|
+
// field is persisted with the session and stripped on the OpenAI path.
|
|
524
|
+
if (reasoningDetails !== undefined) {
|
|
525
|
+
;
|
|
526
|
+
assistantMessage[REASONING_DETAILS_FIELD] = reasoningDetails;
|
|
527
|
+
}
|
|
516
528
|
this.messages.push(assistantMessage);
|
|
517
529
|
if (toolCalls.length === 0) {
|
|
518
530
|
return true;
|
package/dist/core/hooks.js
CHANGED
|
@@ -260,11 +260,11 @@ function capContext(text) {
|
|
|
260
260
|
}
|
|
261
261
|
/** Keys always stripped from the hook environment (OrbCode credentials/config). */
|
|
262
262
|
const HOOK_ENV_REDACT_EXACT = new Set([
|
|
263
|
-
"
|
|
264
|
-
"
|
|
265
|
-
"
|
|
266
|
-
"
|
|
267
|
-
"
|
|
263
|
+
"MATTERAI_TOKEN",
|
|
264
|
+
"MATTERAI_API_KEY",
|
|
265
|
+
"MATTERAI_CONFIG_DIR",
|
|
266
|
+
"MATTERAI_BACKEND_URL",
|
|
267
|
+
"MATTERAI_APP_URL",
|
|
268
268
|
]);
|
|
269
269
|
/** Pattern for credential-like env var names (TOKEN, KEY, SECRET, …) so
|
|
270
270
|
* third-party secrets (GITHUB_TOKEN, AWS_SECRET_ACCESS_KEY, …) are redacted
|
|
@@ -274,7 +274,7 @@ const HOOK_ENV_REDACT_PATTERN = /(?:^|_)(TOKEN|KEY|SECRET|PASSWORD|PASSWD|CREDEN
|
|
|
274
274
|
* (so PATH, HOME, npx, git, … all work) minus anything that looks like a
|
|
275
275
|
* credential — hooks must never see the user's API token. */
|
|
276
276
|
function buildHookEnv(cwd) {
|
|
277
|
-
const env = {
|
|
277
|
+
const env = { MATTERAI_PROJECT_DIR: cwd };
|
|
278
278
|
for (const [key, value] of Object.entries(process.env)) {
|
|
279
279
|
if (value === undefined)
|
|
280
280
|
continue;
|
package/dist/headless.js
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
|
+
import { getModel, isValidAxonModel, usesAiSdk } from "./api/models.js";
|
|
1
2
|
import { getAuthToken, getPendingProjectHooks, loadSettings } from "./config/settings.js";
|
|
2
3
|
import { Agent } from "./core/agent.js";
|
|
3
4
|
/** Non-interactive `orbcode -p "prompt"` mode: prints the final response to stdout. */
|
|
4
5
|
export async function runHeadless(prompt, yolo) {
|
|
5
6
|
const settings = loadSettings();
|
|
7
|
+
// An unknown --model (or MATTERAI_MODEL) silently resolves to the default; say
|
|
8
|
+
// so on stderr instead of quietly running a different model than requested.
|
|
9
|
+
const requestedModel = process.env.MATTERAI_MODEL;
|
|
10
|
+
if (requestedModel && !isValidAxonModel(requestedModel)) {
|
|
11
|
+
process.stderr.write(`warning: unknown model "${requestedModel}"; using "${settings.model}". ` +
|
|
12
|
+
`Add it under "customModels" in settings.json (with a "provider") to use it.\n`);
|
|
13
|
+
}
|
|
6
14
|
const token = getAuthToken(settings);
|
|
7
|
-
|
|
8
|
-
|
|
15
|
+
// MatterAI/Axon models authenticate with the login token. AI-SDK providers
|
|
16
|
+
// (Anthropic, etc.) authenticate with their own key — resolved by the
|
|
17
|
+
// provider from the env (e.g. ANTHROPIC_API_KEY) or the model's `apiKey` —
|
|
18
|
+
// so they don't need a MatterAI login. Only gate on the token when the
|
|
19
|
+
// selected model actually goes through the MatterAI gateway.
|
|
20
|
+
if (!token && !usesAiSdk(getModel(settings.model))) {
|
|
21
|
+
console.error("Not signed in. Run `orbcode login`, set MATTERAI_TOKEN, or put an apiKey in settings.json.");
|
|
9
22
|
process.exit(1);
|
|
10
23
|
}
|
|
11
24
|
// There's no interactive trust prompt in headless mode, so untrusted project
|
|
@@ -13,7 +26,7 @@ export async function runHeadless(prompt, yolo) {
|
|
|
13
26
|
const pendingHooks = getPendingProjectHooks();
|
|
14
27
|
if (pendingHooks) {
|
|
15
28
|
process.stderr.write(`note: ${pendingHooks.commands.length} project hook(s) in .orbcode/settings.json are untrusted and were skipped. ` +
|
|
16
|
-
`Trust them in an interactive session, or set
|
|
29
|
+
`Trust them in an interactive session, or set MATTERAI_TRUST_PROJECT_HOOKS=1.\n`);
|
|
17
30
|
}
|
|
18
31
|
let exitCode = 0;
|
|
19
32
|
// Only the final content is printed: either the attempt_completion result
|
|
@@ -24,7 +37,9 @@ export async function runHeadless(prompt, yolo) {
|
|
|
24
37
|
let completionResult = "";
|
|
25
38
|
const agent = new Agent({
|
|
26
39
|
cwd: process.cwd(),
|
|
27
|
-
|
|
40
|
+
// May be empty for AI-SDK providers; AiSdkClient ignores it and uses the
|
|
41
|
+
// provider's own key. AxonClient only runs when a token is present.
|
|
42
|
+
token: token ?? "",
|
|
28
43
|
modelId: settings.model,
|
|
29
44
|
organizationId: settings.organizationId,
|
|
30
45
|
baseUrl: settings.baseUrl,
|
package/dist/index.js
CHANGED
|
@@ -88,9 +88,9 @@ async function main() {
|
|
|
88
88
|
}
|
|
89
89
|
const model = takeFlagValue(args, "model") ?? takeFlagValue(args, "m");
|
|
90
90
|
if (model) {
|
|
91
|
-
// loadSettings() treats
|
|
91
|
+
// loadSettings() treats MATTERAI_MODEL as the highest-precedence override,
|
|
92
92
|
// so the flag reaches both the TUI and headless mode without plumbing.
|
|
93
|
-
process.env.
|
|
93
|
+
process.env.MATTERAI_MODEL = model;
|
|
94
94
|
}
|
|
95
95
|
let initialSession;
|
|
96
96
|
const resumeId = takeFlagValue(args, "resume") ?? takeFlagValue(args, "r");
|
package/dist/ui/App.js
CHANGED
|
@@ -523,7 +523,7 @@ export function App({ initialView, initialPrompt, initialSession, updateCheck, }
|
|
|
523
523
|
`Version ${VERSION}`,
|
|
524
524
|
`Model ${model.name} (${model.id})`,
|
|
525
525
|
`Directory ${process.cwd()}`,
|
|
526
|
-
`Account ${getAuthToken(settings) ? (settings.apiKey || process.env.
|
|
526
|
+
`Account ${getAuthToken(settings) ? (settings.apiKey || process.env.MATTERAI_TOKEN ? "API key" : "signed in") : "signed out"}${settings.organizationId ? ` · org ${settings.organizationId}` : ""}`,
|
|
527
527
|
`Gateway ${settings.baseUrl ?? "MatterAI (default)"}`,
|
|
528
528
|
`Context ${contextTokens.toLocaleString()} / ${model.contextWindow.toLocaleString()} tokens (${contextPct}%)`,
|
|
529
529
|
`Cost $${totalCost.toFixed(4)} this session`,
|
package/dist/ui/LoginView.js
CHANGED
|
@@ -78,5 +78,5 @@ export function LoginView({ onLogin }) {
|
|
|
78
78
|
void beginBrowserLogin();
|
|
79
79
|
}
|
|
80
80
|
});
|
|
81
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: COLORS.primary, paddingX: 1, children: [_jsxs(Text, { bold: true, color: COLORS.primary, children: ["Sign in to ", PRODUCT_NAME] }), phase === "waiting" ? (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: "Waiting for authorization in your browser" }) }), _jsx(Text, { dimColor: true, children: "Approve the \"Authorize OrbCode CLI\" dialog at:" }), _jsxs(Text, { dimColor: true, children: [" ", authorizeUrl] }), _jsx(Text, { dimColor: true, children: "Esc to cancel" })] })) : (_jsxs(Text, { children: ["Press ", _jsx(Text, { bold: true, children: "Enter" }), " to open MatterAI in your browser and authorize OrbCode CLI."] })), phase === "starting" && _jsx(Text, { color: COLORS.thinking, children: "Contacting sign-in service\u2026" }), phase === "verifying" && _jsx(Text, { color: COLORS.thinking, children: "Verifying\u2026" }), error && _jsxs(Text, { color: COLORS.error, children: ["\u2717 ", error] }), _jsx(Text, { dimColor: true, children: "To use a token instead, set apiKey in ~/.orbcode/settings.json or the
|
|
81
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: COLORS.primary, paddingX: 1, children: [_jsxs(Text, { bold: true, color: COLORS.primary, children: ["Sign in to ", PRODUCT_NAME] }), phase === "waiting" ? (_jsxs(_Fragment, { children: [_jsx(Box, { marginTop: 1, children: _jsx(Spinner, { label: "Waiting for authorization in your browser" }) }), _jsx(Text, { dimColor: true, children: "Approve the \"Authorize OrbCode CLI\" dialog at:" }), _jsxs(Text, { dimColor: true, children: [" ", authorizeUrl] }), _jsx(Text, { dimColor: true, children: "Esc to cancel" })] })) : (_jsxs(Text, { children: ["Press ", _jsx(Text, { bold: true, children: "Enter" }), " to open MatterAI in your browser and authorize OrbCode CLI."] })), phase === "starting" && _jsx(Text, { color: COLORS.thinking, children: "Contacting sign-in service\u2026" }), phase === "verifying" && _jsx(Text, { color: COLORS.thinking, children: "Verifying\u2026" }), error && _jsxs(Text, { color: COLORS.error, children: ["\u2717 ", error] }), _jsx(Text, { dimColor: true, children: "To use a token instead, set apiKey in ~/.orbcode/settings.json or the MATTERAI_TOKEN env var." })] }));
|
|
82
82
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matterailab/orbcode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "OrbCode CLI — agentic coding in your terminal, powered by Axon models by MatterAI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -36,6 +36,9 @@
|
|
|
36
36
|
"prepublishOnly": "npm run typecheck && npm run build"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
+
"@ai-sdk/anthropic": "^3.0.85",
|
|
40
|
+
"@ai-sdk/openai-compatible": "^2.0.51",
|
|
41
|
+
"ai": "^6.0.207",
|
|
39
42
|
"chalk": "^5.3.0",
|
|
40
43
|
"ink": "^5.2.1",
|
|
41
44
|
"open": "^10.1.0",
|