faces-cli 1.5.15 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +14 -1
- package/dist/client.js +40 -6
- package/dist/commands/account/preferences.d.ts +15 -0
- package/dist/commands/account/preferences.js +65 -0
- package/dist/commands/auth/register.d.ts +0 -1
- package/dist/commands/auth/register.js +1 -3
- package/dist/commands/billing/checkout.js +1 -1
- package/dist/commands/chat/chat.d.ts +2 -0
- package/dist/commands/chat/chat.js +88 -18
- package/dist/commands/chat/messages.d.ts +1 -0
- package/dist/commands/chat/messages.js +25 -6
- package/dist/commands/chat/responses.d.ts +1 -0
- package/dist/commands/chat/responses.js +25 -6
- package/dist/commands/compile/thread/create.d.ts +1 -0
- package/dist/commands/compile/thread/create.js +11 -1
- package/dist/commands/compile/thread/message.d.ts +1 -0
- package/dist/commands/compile/thread/message.js +13 -2
- package/oclif.manifest.json +383 -293
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
export declare class FacesAPIError extends Error {
|
|
2
2
|
statusCode: number;
|
|
3
|
-
|
|
3
|
+
errorCode?: string;
|
|
4
|
+
fallbackAvailable?: boolean;
|
|
5
|
+
constructor(statusCode: number, message: string, opts?: {
|
|
6
|
+
errorCode?: string;
|
|
7
|
+
fallbackAvailable?: boolean;
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export interface ResponseWithHeaders<T = unknown> {
|
|
11
|
+
data: T;
|
|
12
|
+
headers: Record<string, string>;
|
|
4
13
|
}
|
|
5
14
|
export declare class FacesClient {
|
|
6
15
|
private baseUrl;
|
|
@@ -19,6 +28,10 @@ export declare class FacesClient {
|
|
|
19
28
|
requireJwt?: boolean;
|
|
20
29
|
body?: unknown;
|
|
21
30
|
}): Promise<unknown>;
|
|
31
|
+
postWithHeaders(path: string, opts?: {
|
|
32
|
+
requireJwt?: boolean;
|
|
33
|
+
body?: unknown;
|
|
34
|
+
}): Promise<ResponseWithHeaders>;
|
|
22
35
|
postNoAuth(path: string, body: unknown): Promise<unknown>;
|
|
23
36
|
patch(path: string, opts?: {
|
|
24
37
|
requireJwt?: boolean;
|
package/dist/client.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
export class FacesAPIError extends Error {
|
|
2
2
|
statusCode;
|
|
3
|
-
|
|
3
|
+
errorCode;
|
|
4
|
+
fallbackAvailable;
|
|
5
|
+
constructor(statusCode, message, opts) {
|
|
4
6
|
super(message);
|
|
5
7
|
this.statusCode = statusCode;
|
|
6
8
|
this.name = 'FacesAPIError';
|
|
9
|
+
this.errorCode = opts?.errorCode;
|
|
10
|
+
this.fallbackAvailable = opts?.fallbackAvailable;
|
|
7
11
|
}
|
|
8
12
|
}
|
|
9
13
|
export class FacesClient {
|
|
@@ -40,14 +44,24 @@ export class FacesClient {
|
|
|
40
44
|
}
|
|
41
45
|
async parseError(resp) {
|
|
42
46
|
let msg = resp.statusText;
|
|
47
|
+
let errorCode;
|
|
48
|
+
let fallbackAvailable;
|
|
43
49
|
try {
|
|
44
50
|
const body = await resp.json();
|
|
45
|
-
|
|
46
|
-
if (
|
|
47
|
-
|
|
51
|
+
// Structured OAuth rejection (422)
|
|
52
|
+
if (body.error === 'oauth_rejected') {
|
|
53
|
+
errorCode = 'oauth_rejected';
|
|
54
|
+
fallbackAvailable = body.fallback_available;
|
|
55
|
+
msg = String(body.detail ?? 'OAuth request rejected');
|
|
48
56
|
}
|
|
49
57
|
else {
|
|
50
|
-
|
|
58
|
+
const raw = body.detail ?? body.error ?? body.message ?? msg;
|
|
59
|
+
if (typeof raw === 'object' && raw !== null && 'message' in raw) {
|
|
60
|
+
msg = String(raw.message);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
msg = typeof raw === 'object' ? JSON.stringify(raw) : String(raw);
|
|
64
|
+
}
|
|
51
65
|
}
|
|
52
66
|
}
|
|
53
67
|
catch {
|
|
@@ -57,7 +71,7 @@ export class FacesClient {
|
|
|
57
71
|
msg = `Payment required: ${msg}`;
|
|
58
72
|
if (resp.status === 403)
|
|
59
73
|
msg = `Forbidden: ${msg}`;
|
|
60
|
-
return new FacesAPIError(resp.status, msg);
|
|
74
|
+
return new FacesAPIError(resp.status, msg, { errorCode, fallbackAvailable });
|
|
61
75
|
}
|
|
62
76
|
async get(path, opts = {}) {
|
|
63
77
|
let url = this.url(path);
|
|
@@ -91,6 +105,26 @@ export class FacesClient {
|
|
|
91
105
|
throw await this.parseError(resp);
|
|
92
106
|
return resp.json();
|
|
93
107
|
}
|
|
108
|
+
async postWithHeaders(path, opts = {}) {
|
|
109
|
+
let resp;
|
|
110
|
+
try {
|
|
111
|
+
resp = await fetch(this.url(path), {
|
|
112
|
+
method: 'POST',
|
|
113
|
+
headers: { ...this.authHeader(opts.requireJwt), 'Content-Type': 'application/json' },
|
|
114
|
+
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
this.wrapNetworkError(err, path);
|
|
119
|
+
}
|
|
120
|
+
if (!resp.ok)
|
|
121
|
+
throw await this.parseError(resp);
|
|
122
|
+
const headers = {};
|
|
123
|
+
for (const [k, v] of resp.headers.entries())
|
|
124
|
+
headers[k] = v;
|
|
125
|
+
const data = await resp.json();
|
|
126
|
+
return { data, headers };
|
|
127
|
+
}
|
|
94
128
|
async postNoAuth(path, body) {
|
|
95
129
|
let resp;
|
|
96
130
|
try {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base.js';
|
|
2
|
+
export default class AccountPreferences extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
|
+
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
static args: {
|
|
10
|
+
key: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
11
|
+
value: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
12
|
+
};
|
|
13
|
+
static examples: string[];
|
|
14
|
+
run(): Promise<unknown>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { BaseCommand } from '../../base.js';
|
|
3
|
+
import { FacesAPIError } from '../../client.js';
|
|
4
|
+
export default class AccountPreferences extends BaseCommand {
|
|
5
|
+
static description = 'View or update account preferences (api_fallback, default_model)';
|
|
6
|
+
static flags = {
|
|
7
|
+
...BaseCommand.baseFlags,
|
|
8
|
+
};
|
|
9
|
+
static args = {
|
|
10
|
+
key: Args.string({ description: 'Preference key to update (api_fallback, default_model)' }),
|
|
11
|
+
value: Args.string({ description: 'New value' }),
|
|
12
|
+
};
|
|
13
|
+
static examples = [
|
|
14
|
+
'faces account:preferences',
|
|
15
|
+
'faces account:preferences default_model gpt-5.4',
|
|
16
|
+
'faces account:preferences api_fallback true',
|
|
17
|
+
];
|
|
18
|
+
async run() {
|
|
19
|
+
const { args, flags } = await this.parse(AccountPreferences);
|
|
20
|
+
const client = this.makeClient(flags);
|
|
21
|
+
// If no key provided, show current preferences
|
|
22
|
+
if (!args.key) {
|
|
23
|
+
let data;
|
|
24
|
+
try {
|
|
25
|
+
data = await client.get('/v1/user/preferences');
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
if (err instanceof FacesAPIError)
|
|
29
|
+
this.error(`Error (${err.statusCode}): ${err.message}`);
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
if (!this.jsonEnabled())
|
|
33
|
+
this.printHuman(data);
|
|
34
|
+
return data;
|
|
35
|
+
}
|
|
36
|
+
// Update a preference
|
|
37
|
+
if (!args.value)
|
|
38
|
+
this.error('Provide a value to set.');
|
|
39
|
+
const validKeys = ['api_fallback', 'default_model'];
|
|
40
|
+
if (!validKeys.includes(args.key)) {
|
|
41
|
+
this.error(`Unknown preference '${args.key}'. Valid keys: ${validKeys.join(', ')}`);
|
|
42
|
+
}
|
|
43
|
+
let parsedValue = args.value;
|
|
44
|
+
if (args.key === 'api_fallback') {
|
|
45
|
+
if (args.value !== 'true' && args.value !== 'false') {
|
|
46
|
+
this.error('api_fallback must be "true" or "false"');
|
|
47
|
+
}
|
|
48
|
+
parsedValue = args.value === 'true';
|
|
49
|
+
}
|
|
50
|
+
let data;
|
|
51
|
+
try {
|
|
52
|
+
data = await client.patch('/v1/user/preferences', { body: { [args.key]: parsedValue } });
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
if (err instanceof FacesAPIError)
|
|
56
|
+
this.error(`Error (${err.statusCode}): ${err.message}`);
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
if (!this.jsonEnabled()) {
|
|
60
|
+
this.log(`Set ${args.key} = ${args.value}`);
|
|
61
|
+
this.printHuman(data);
|
|
62
|
+
}
|
|
63
|
+
return data;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -4,7 +4,6 @@ export default class AuthRegister extends BaseCommand {
|
|
|
4
4
|
static flags: {
|
|
5
5
|
email: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
6
|
password: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
-
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
7
|
username: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
8
|
'invite-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
9
|
plan: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -8,7 +8,6 @@ export default class AuthRegister extends BaseCommand {
|
|
|
8
8
|
...BaseCommand.baseFlags,
|
|
9
9
|
email: Flags.string({ description: 'Email address', required: true }),
|
|
10
10
|
password: Flags.string({ description: 'Password', required: true }),
|
|
11
|
-
name: Flags.string({ description: 'Display name (defaults to username)' }),
|
|
12
11
|
username: Flags.string({ description: 'Username (lowercase, dashes, numbers)', required: true }),
|
|
13
12
|
'invite-key': Flags.string({ description: 'Invite key (if required)' }),
|
|
14
13
|
plan: Flags.string({
|
|
@@ -24,7 +23,6 @@ export default class AuthRegister extends BaseCommand {
|
|
|
24
23
|
const payload = {
|
|
25
24
|
email: flags.email,
|
|
26
25
|
password: flags.password,
|
|
27
|
-
name: flags.name ?? flags.username,
|
|
28
26
|
username: flags.username,
|
|
29
27
|
source: 'cli',
|
|
30
28
|
};
|
|
@@ -55,7 +53,7 @@ export default class AuthRegister extends BaseCommand {
|
|
|
55
53
|
if (flags.plan === 'connect') {
|
|
56
54
|
// Connect plan: $17/mo subscription, no $5 activation needed
|
|
57
55
|
try {
|
|
58
|
-
const checkoutResp = await authedClient.post(`/v1/billing/checkout?plan=connect`);
|
|
56
|
+
const checkoutResp = await authedClient.post(`/v1/billing/checkout?plan=connect&source=cli`);
|
|
59
57
|
result.checkout_url = checkoutResp.checkout_url;
|
|
60
58
|
result.plan = 'connect';
|
|
61
59
|
result.amount = '$17/mo';
|
|
@@ -16,7 +16,7 @@ export default class BillingCheckout extends BaseCommand {
|
|
|
16
16
|
const client = this.makeClient(flags);
|
|
17
17
|
let data;
|
|
18
18
|
try {
|
|
19
|
-
data = await client.post(`/v1/billing/checkout?plan=${flags.plan}`);
|
|
19
|
+
data = await client.post(`/v1/billing/checkout?plan=${flags.plan}&source=cli`);
|
|
20
20
|
}
|
|
21
21
|
catch (err) {
|
|
22
22
|
if (err instanceof FacesAPIError)
|
|
@@ -10,6 +10,7 @@ export default class ChatChat extends BaseCommand {
|
|
|
10
10
|
temperature: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
11
|
file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
12
|
responses: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
'oauth-only': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
14
|
'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
15
|
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
16
|
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -21,4 +22,5 @@ export default class ChatChat extends BaseCommand {
|
|
|
21
22
|
private runChatCompletions;
|
|
22
23
|
private runMessages;
|
|
23
24
|
private runResponses;
|
|
25
|
+
private extractText;
|
|
24
26
|
}
|
|
@@ -20,6 +20,7 @@ export default class ChatChat extends BaseCommand {
|
|
|
20
20
|
temperature: Flags.string({ description: 'Temperature (0.0-2.0)' }),
|
|
21
21
|
file: Flags.string({ description: 'Read message from file' }),
|
|
22
22
|
responses: Flags.boolean({ description: 'Use OpenAI Responses API instead of Chat Completions', default: false }),
|
|
23
|
+
'oauth-only': Flags.boolean({ description: 'Prevent fallback to paid system key (use OAuth only)', default: false }),
|
|
23
24
|
};
|
|
24
25
|
static args = {
|
|
25
26
|
face_username: Args.string({ description: 'Face alias (or alias@model)', required: true }),
|
|
@@ -64,8 +65,15 @@ export default class ChatChat extends BaseCommand {
|
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
catch (err) {
|
|
67
|
-
if (err instanceof FacesAPIError)
|
|
68
|
+
if (err instanceof FacesAPIError) {
|
|
69
|
+
if (err.errorCode === 'oauth_rejected') {
|
|
70
|
+
const hint = err.fallbackAvailable
|
|
71
|
+
? 'Run `faces account:preferences api_fallback true` to allow paid fallback.'
|
|
72
|
+
: 'Run `faces billing:topup` to add credits, then enable fallback with `faces account:preferences api_fallback true`.';
|
|
73
|
+
this.error(`OAuth failed: ${err.message}\n${hint}`);
|
|
74
|
+
}
|
|
68
75
|
this.error(`Error (${err.statusCode}): ${err.message}`);
|
|
76
|
+
}
|
|
69
77
|
throw err;
|
|
70
78
|
}
|
|
71
79
|
}
|
|
@@ -80,6 +88,8 @@ export default class ChatChat extends BaseCommand {
|
|
|
80
88
|
payload.max_tokens = flags['max-tokens'];
|
|
81
89
|
if (flags.temperature !== undefined)
|
|
82
90
|
payload.temperature = Number.parseFloat(flags.temperature);
|
|
91
|
+
if (flags['oauth-only'])
|
|
92
|
+
payload.oauth_only = true;
|
|
83
93
|
if (flags.stream) {
|
|
84
94
|
for await (const line of client.stream('/v1/chat/completions', payload)) {
|
|
85
95
|
if (!line.startsWith('data: '))
|
|
@@ -99,13 +109,25 @@ export default class ChatChat extends BaseCommand {
|
|
|
99
109
|
process.stdout.write('\n');
|
|
100
110
|
return {};
|
|
101
111
|
}
|
|
102
|
-
const data =
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
this.
|
|
108
|
-
|
|
112
|
+
const { data, headers } = await client.postWithHeaders('/v1/chat/completions', { body: payload });
|
|
113
|
+
const body = data;
|
|
114
|
+
const routedEndpoint = headers['x-faces-routed-endpoint'];
|
|
115
|
+
// Extract text based on which format the backend used
|
|
116
|
+
const text = this.extractText(body, routedEndpoint);
|
|
117
|
+
if (this.jsonEnabled()) {
|
|
118
|
+
const meta = {};
|
|
119
|
+
if (headers['x-faces-provider'])
|
|
120
|
+
meta.provider = headers['x-faces-provider'];
|
|
121
|
+
if (headers['x-faces-cost-usd'])
|
|
122
|
+
meta.cost_usd = headers['x-faces-cost-usd'];
|
|
123
|
+
if (routedEndpoint)
|
|
124
|
+
meta.routed_endpoint = routedEndpoint;
|
|
125
|
+
if (Object.keys(meta).length > 0)
|
|
126
|
+
body._meta = meta;
|
|
127
|
+
return body;
|
|
128
|
+
}
|
|
129
|
+
this.log(text);
|
|
130
|
+
return body;
|
|
109
131
|
}
|
|
110
132
|
async runMessages(client, model, userMessages, flags) {
|
|
111
133
|
const msgs = userMessages.map((m) => ({ role: 'user', content: m }));
|
|
@@ -117,6 +139,8 @@ export default class ChatChat extends BaseCommand {
|
|
|
117
139
|
};
|
|
118
140
|
if (flags.system)
|
|
119
141
|
payload.system = flags.system;
|
|
142
|
+
if (flags['oauth-only'])
|
|
143
|
+
payload.oauth_only = true;
|
|
120
144
|
if (flags.stream) {
|
|
121
145
|
for await (const line of client.stream('/v1/messages', payload)) {
|
|
122
146
|
if (!line.startsWith('data: '))
|
|
@@ -137,21 +161,32 @@ export default class ChatChat extends BaseCommand {
|
|
|
137
161
|
process.stdout.write('\n');
|
|
138
162
|
return {};
|
|
139
163
|
}
|
|
140
|
-
const data =
|
|
141
|
-
|
|
142
|
-
|
|
164
|
+
const { data, headers } = await client.postWithHeaders('/v1/messages', { body: payload });
|
|
165
|
+
const body = data;
|
|
166
|
+
if (this.jsonEnabled()) {
|
|
167
|
+
const meta = {};
|
|
168
|
+
if (headers['x-faces-provider'])
|
|
169
|
+
meta.provider = headers['x-faces-provider'];
|
|
170
|
+
if (headers['x-faces-cost-usd'])
|
|
171
|
+
meta.cost_usd = headers['x-faces-cost-usd'];
|
|
172
|
+
if (Object.keys(meta).length > 0)
|
|
173
|
+
body._meta = meta;
|
|
174
|
+
return body;
|
|
175
|
+
}
|
|
143
176
|
let content = '';
|
|
144
|
-
for (const block of
|
|
177
|
+
for (const block of body.content ?? []) {
|
|
145
178
|
if (block.type === 'text')
|
|
146
179
|
content += String(block.text ?? '');
|
|
147
180
|
}
|
|
148
181
|
this.log(content);
|
|
149
|
-
return
|
|
182
|
+
return body;
|
|
150
183
|
}
|
|
151
184
|
async runResponses(client, model, input, flags) {
|
|
152
185
|
const payload = { model, input, stream: flags.stream };
|
|
153
186
|
if (flags.system)
|
|
154
187
|
payload.instructions = flags.system;
|
|
188
|
+
if (flags['oauth-only'])
|
|
189
|
+
payload.oauth_only = true;
|
|
155
190
|
if (flags.stream) {
|
|
156
191
|
for await (const line of client.stream('/v1/responses', payload)) {
|
|
157
192
|
if (!line.startsWith('data: '))
|
|
@@ -172,17 +207,52 @@ export default class ChatChat extends BaseCommand {
|
|
|
172
207
|
process.stdout.write('\n');
|
|
173
208
|
return {};
|
|
174
209
|
}
|
|
175
|
-
const data =
|
|
176
|
-
|
|
177
|
-
|
|
210
|
+
const { data, headers } = await client.postWithHeaders('/v1/responses', { body: payload });
|
|
211
|
+
const body = data;
|
|
212
|
+
if (this.jsonEnabled()) {
|
|
213
|
+
const meta = {};
|
|
214
|
+
if (headers['x-faces-provider'])
|
|
215
|
+
meta.provider = headers['x-faces-provider'];
|
|
216
|
+
if (headers['x-faces-cost-usd'])
|
|
217
|
+
meta.cost_usd = headers['x-faces-cost-usd'];
|
|
218
|
+
if (Object.keys(meta).length > 0)
|
|
219
|
+
body._meta = meta;
|
|
220
|
+
return body;
|
|
221
|
+
}
|
|
178
222
|
let outputText = '';
|
|
179
|
-
for (const item of
|
|
223
|
+
for (const item of body.output ?? []) {
|
|
180
224
|
for (const block of item.content ?? []) {
|
|
181
225
|
if (block.type === 'output_text')
|
|
182
226
|
outputText += String(block.text ?? '');
|
|
183
227
|
}
|
|
184
228
|
}
|
|
185
229
|
this.log(outputText);
|
|
186
|
-
return
|
|
230
|
+
return body;
|
|
231
|
+
}
|
|
232
|
+
extractText(body, routedEndpoint) {
|
|
233
|
+
if (routedEndpoint === '/v1/responses') {
|
|
234
|
+
// OpenAI Responses format
|
|
235
|
+
let text = '';
|
|
236
|
+
for (const item of body.output ?? []) {
|
|
237
|
+
for (const block of item.content ?? []) {
|
|
238
|
+
if (block.type === 'output_text')
|
|
239
|
+
text += String(block.text ?? '');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return text;
|
|
243
|
+
}
|
|
244
|
+
if (routedEndpoint === '/v1/messages') {
|
|
245
|
+
// Anthropic Messages format
|
|
246
|
+
let text = '';
|
|
247
|
+
for (const block of body.content ?? []) {
|
|
248
|
+
if (block.type === 'text')
|
|
249
|
+
text += String(block.text ?? '');
|
|
250
|
+
}
|
|
251
|
+
return text;
|
|
252
|
+
}
|
|
253
|
+
// Standard chat/completions format (default)
|
|
254
|
+
const choices = body.choices;
|
|
255
|
+
const message = choices?.[0]?.message;
|
|
256
|
+
return String(message?.content ?? '');
|
|
187
257
|
}
|
|
188
258
|
}
|
|
@@ -6,6 +6,7 @@ export default class ChatMessages extends BaseCommand {
|
|
|
6
6
|
system: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
7
|
stream: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
8
|
'max-tokens': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
'oauth-only': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
10
|
'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
11
|
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
12
|
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -9,6 +9,7 @@ export default class ChatMessages extends BaseCommand {
|
|
|
9
9
|
system: Flags.string({ description: 'System prompt' }),
|
|
10
10
|
stream: Flags.boolean({ description: 'Stream the response', default: false }),
|
|
11
11
|
'max-tokens': Flags.integer({ description: 'Max tokens', default: 1024 }),
|
|
12
|
+
'oauth-only': Flags.boolean({ description: 'Prevent fallback to paid system key (use OAuth only)', default: false }),
|
|
12
13
|
};
|
|
13
14
|
static args = {
|
|
14
15
|
face_model: Args.string({ description: 'Face model (e.g. myface or myface@claude-sonnet-4-6)', required: true }),
|
|
@@ -25,6 +26,8 @@ export default class ChatMessages extends BaseCommand {
|
|
|
25
26
|
};
|
|
26
27
|
if (flags.system)
|
|
27
28
|
payload.system = flags.system;
|
|
29
|
+
if (flags['oauth-only'])
|
|
30
|
+
payload.oauth_only = true;
|
|
28
31
|
try {
|
|
29
32
|
if (flags.stream) {
|
|
30
33
|
for await (const line of client.stream('/v1/messages', payload)) {
|
|
@@ -48,20 +51,36 @@ export default class ChatMessages extends BaseCommand {
|
|
|
48
51
|
process.stdout.write('\n');
|
|
49
52
|
return {};
|
|
50
53
|
}
|
|
51
|
-
const data =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
const { data, headers } = await client.postWithHeaders('/v1/messages', { body: payload });
|
|
55
|
+
const body = data;
|
|
56
|
+
if (this.jsonEnabled()) {
|
|
57
|
+
const meta = {};
|
|
58
|
+
if (headers['x-faces-provider'])
|
|
59
|
+
meta.provider = headers['x-faces-provider'];
|
|
60
|
+
if (headers['x-faces-cost-usd'])
|
|
61
|
+
meta.cost_usd = headers['x-faces-cost-usd'];
|
|
62
|
+
if (Object.keys(meta).length > 0)
|
|
63
|
+
body._meta = meta;
|
|
64
|
+
return body;
|
|
65
|
+
}
|
|
54
66
|
let content = '';
|
|
55
|
-
for (const block of
|
|
67
|
+
for (const block of body.content ?? []) {
|
|
56
68
|
if (block.type === 'text')
|
|
57
69
|
content += String(block.text ?? '');
|
|
58
70
|
}
|
|
59
71
|
this.log(content);
|
|
60
|
-
return
|
|
72
|
+
return body;
|
|
61
73
|
}
|
|
62
74
|
catch (err) {
|
|
63
|
-
if (err instanceof FacesAPIError)
|
|
75
|
+
if (err instanceof FacesAPIError) {
|
|
76
|
+
if (err.errorCode === 'oauth_rejected') {
|
|
77
|
+
const hint = err.fallbackAvailable
|
|
78
|
+
? 'Run `faces account:preferences api_fallback true` to allow paid fallback.'
|
|
79
|
+
: 'Run `faces billing:topup` to add credits, then enable fallback with `faces account:preferences api_fallback true`.';
|
|
80
|
+
this.error(`OAuth failed: ${err.message}\n${hint}`);
|
|
81
|
+
}
|
|
64
82
|
this.error(`Error (${err.statusCode}): ${err.message}`);
|
|
83
|
+
}
|
|
65
84
|
throw err;
|
|
66
85
|
}
|
|
67
86
|
}
|
|
@@ -5,6 +5,7 @@ export default class ChatResponses extends BaseCommand {
|
|
|
5
5
|
message: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
6
|
instructions: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
7
|
stream: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
'oauth-only': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
9
|
'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
10
|
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
11
|
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -8,6 +8,7 @@ export default class ChatResponses extends BaseCommand {
|
|
|
8
8
|
message: Flags.string({ char: 'm', description: 'User input message', required: true }),
|
|
9
9
|
instructions: Flags.string({ description: 'System instructions' }),
|
|
10
10
|
stream: Flags.boolean({ description: 'Stream the response', default: false }),
|
|
11
|
+
'oauth-only': Flags.boolean({ description: 'Prevent fallback to paid system key (use OAuth only)', default: false }),
|
|
11
12
|
};
|
|
12
13
|
static args = {
|
|
13
14
|
face_model: Args.string({ description: 'Face model (e.g. myface or myface@gpt-4o)', required: true }),
|
|
@@ -22,6 +23,8 @@ export default class ChatResponses extends BaseCommand {
|
|
|
22
23
|
};
|
|
23
24
|
if (flags.instructions)
|
|
24
25
|
payload.instructions = flags.instructions;
|
|
26
|
+
if (flags['oauth-only'])
|
|
27
|
+
payload.oauth_only = true;
|
|
25
28
|
try {
|
|
26
29
|
if (flags.stream) {
|
|
27
30
|
for await (const line of client.stream('/v1/responses', payload)) {
|
|
@@ -45,22 +48,38 @@ export default class ChatResponses extends BaseCommand {
|
|
|
45
48
|
process.stdout.write('\n');
|
|
46
49
|
return {};
|
|
47
50
|
}
|
|
48
|
-
const data =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
const { data, headers } = await client.postWithHeaders('/v1/responses', { body: payload });
|
|
52
|
+
const body = data;
|
|
53
|
+
if (this.jsonEnabled()) {
|
|
54
|
+
const meta = {};
|
|
55
|
+
if (headers['x-faces-provider'])
|
|
56
|
+
meta.provider = headers['x-faces-provider'];
|
|
57
|
+
if (headers['x-faces-cost-usd'])
|
|
58
|
+
meta.cost_usd = headers['x-faces-cost-usd'];
|
|
59
|
+
if (Object.keys(meta).length > 0)
|
|
60
|
+
body._meta = meta;
|
|
61
|
+
return body;
|
|
62
|
+
}
|
|
51
63
|
let outputText = '';
|
|
52
|
-
for (const item of
|
|
64
|
+
for (const item of body.output ?? []) {
|
|
53
65
|
for (const block of item.content ?? []) {
|
|
54
66
|
if (block.type === 'output_text')
|
|
55
67
|
outputText += String(block.text ?? '');
|
|
56
68
|
}
|
|
57
69
|
}
|
|
58
70
|
this.log(outputText);
|
|
59
|
-
return
|
|
71
|
+
return body;
|
|
60
72
|
}
|
|
61
73
|
catch (err) {
|
|
62
|
-
if (err instanceof FacesAPIError)
|
|
74
|
+
if (err instanceof FacesAPIError) {
|
|
75
|
+
if (err.errorCode === 'oauth_rejected') {
|
|
76
|
+
const hint = err.fallbackAvailable
|
|
77
|
+
? 'Run `faces account:preferences api_fallback true` to allow paid fallback.'
|
|
78
|
+
: 'Run `faces billing:topup` to add credits, then enable fallback with `faces account:preferences api_fallback true`.';
|
|
79
|
+
this.error(`OAuth failed: ${err.message}\n${hint}`);
|
|
80
|
+
}
|
|
63
81
|
this.error(`Error (${err.statusCode}): ${err.message}`);
|
|
82
|
+
}
|
|
64
83
|
throw err;
|
|
65
84
|
}
|
|
66
85
|
}
|
|
@@ -3,6 +3,7 @@ export default class CompileThreadCreate extends BaseCommand {
|
|
|
3
3
|
static description: string;
|
|
4
4
|
static flags: {
|
|
5
5
|
label: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
|
+
'oauth-only': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
6
7
|
'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
8
|
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
9
|
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -6,6 +6,7 @@ export default class CompileThreadCreate extends BaseCommand {
|
|
|
6
6
|
static flags = {
|
|
7
7
|
...BaseCommand.baseFlags,
|
|
8
8
|
label: Flags.string({ description: 'Thread label' }),
|
|
9
|
+
'oauth-only': Flags.boolean({ description: 'Prevent fallback to paid system key (use OAuth only)', default: false }),
|
|
9
10
|
};
|
|
10
11
|
static args = {
|
|
11
12
|
face_id: Args.string({ description: 'Face alias', required: true }),
|
|
@@ -16,13 +17,22 @@ export default class CompileThreadCreate extends BaseCommand {
|
|
|
16
17
|
const payload = { alias: args.face_id };
|
|
17
18
|
if (flags.label)
|
|
18
19
|
payload.label = flags.label;
|
|
20
|
+
if (flags['oauth-only'])
|
|
21
|
+
payload.oauth_only = true;
|
|
19
22
|
let data;
|
|
20
23
|
try {
|
|
21
24
|
data = await client.post('/v1/compile/threads', { body: payload });
|
|
22
25
|
}
|
|
23
26
|
catch (err) {
|
|
24
|
-
if (err instanceof FacesAPIError)
|
|
27
|
+
if (err instanceof FacesAPIError) {
|
|
28
|
+
if (err.errorCode === 'oauth_rejected') {
|
|
29
|
+
const hint = err.fallbackAvailable
|
|
30
|
+
? 'Run `faces account:preferences api_fallback true` to allow paid fallback.'
|
|
31
|
+
: 'Run `faces billing:topup` to add credits, then enable fallback with `faces account:preferences api_fallback true`.';
|
|
32
|
+
this.error(`OAuth failed: ${err.message}\n${hint}`);
|
|
33
|
+
}
|
|
25
34
|
this.error(`Error (${err.statusCode}): ${err.message}`);
|
|
35
|
+
}
|
|
26
36
|
throw err;
|
|
27
37
|
}
|
|
28
38
|
if (!this.jsonEnabled())
|
|
@@ -3,6 +3,7 @@ export default class CompileThreadMessage extends BaseCommand {
|
|
|
3
3
|
static description: string;
|
|
4
4
|
static flags: {
|
|
5
5
|
message: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
|
+
'oauth-only': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
6
7
|
'base-url': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
8
|
token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
9
|
'api-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -6,6 +6,7 @@ export default class CompileThreadMessage extends BaseCommand {
|
|
|
6
6
|
static flags = {
|
|
7
7
|
...BaseCommand.baseFlags,
|
|
8
8
|
message: Flags.string({ char: 'm', description: 'User message to append', required: true }),
|
|
9
|
+
'oauth-only': Flags.boolean({ description: 'Prevent fallback to paid system key (use OAuth only)', default: false }),
|
|
9
10
|
};
|
|
10
11
|
static args = {
|
|
11
12
|
thread_id: Args.string({ description: 'Thread ID', required: true }),
|
|
@@ -13,15 +14,25 @@ export default class CompileThreadMessage extends BaseCommand {
|
|
|
13
14
|
async run() {
|
|
14
15
|
const { args, flags } = await this.parse(CompileThreadMessage);
|
|
15
16
|
const client = this.makeClient(flags);
|
|
17
|
+
const payload = { message: flags.message };
|
|
18
|
+
if (flags['oauth-only'])
|
|
19
|
+
payload.oauth_only = true;
|
|
16
20
|
let data;
|
|
17
21
|
try {
|
|
18
22
|
data = await client.post(`/v1/compile/threads/${args.thread_id}/messages`, {
|
|
19
|
-
body:
|
|
23
|
+
body: payload,
|
|
20
24
|
});
|
|
21
25
|
}
|
|
22
26
|
catch (err) {
|
|
23
|
-
if (err instanceof FacesAPIError)
|
|
27
|
+
if (err instanceof FacesAPIError) {
|
|
28
|
+
if (err.errorCode === 'oauth_rejected') {
|
|
29
|
+
const hint = err.fallbackAvailable
|
|
30
|
+
? 'Run `faces account:preferences api_fallback true` to allow paid fallback.'
|
|
31
|
+
: 'Run `faces billing:topup` to add credits, then enable fallback with `faces account:preferences api_fallback true`.';
|
|
32
|
+
this.error(`OAuth failed: ${err.message}\n${hint}`);
|
|
33
|
+
}
|
|
24
34
|
this.error(`Error (${err.statusCode}): ${err.message}`);
|
|
35
|
+
}
|
|
25
36
|
throw err;
|
|
26
37
|
}
|
|
27
38
|
if (!this.jsonEnabled())
|