@xfey/tutti 0.1.44 → 0.1.45

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.
@@ -5,7 +5,7 @@ import { REDACTION_LABELS } from "@tutti/shared/utils";
5
5
  import { OPENAI_API_KEY_AUTH_KIND, OPENAI_PROVIDER } from "./model-config.js";
6
6
  import { readJsonFile, writePrivateJsonFile } from "./machine-local-files.js";
7
7
  const CREDENTIAL_REF_PATTERN = /^cred_[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
8
- const OPENAI_API_KEY_PATTERN = /^sk-[A-Za-z0-9_-]{8,}$/u;
8
+ const OPENAI_API_KEY_PATTERN = /^[\x21-\x7E]{8,4096}$/u;
9
9
  function isRecord(value) {
10
10
  return typeof value === "object" && value !== null && !Array.isArray(value);
11
11
  }
@@ -35,7 +35,11 @@ export function createRedactedOpenAiApiKeyLabel(apiKey) {
35
35
  if (!isOpenAiApiKey(apiKey)) {
36
36
  return REDACTION_LABELS.providerApiKey;
37
37
  }
38
- const prefix = apiKey.startsWith("sk-proj-") ? "sk-proj" : "sk";
38
+ const prefix = apiKey.startsWith("sk-proj-")
39
+ ? "sk-proj"
40
+ : apiKey.startsWith("sk-")
41
+ ? "sk"
42
+ : "key";
39
43
  return `${prefix}...${apiKey.slice(-4)}`;
40
44
  }
41
45
  export function resolveOpenAiCredentialPath(tuttiHome, projectId, credentialRef) {
@@ -1,4 +1,4 @@
1
- import OpenAI, { APIConnectionError, APIConnectionTimeoutError, APIError, AuthenticationError, NotFoundError, PermissionDeniedError, RateLimitError, } from "openai";
1
+ import OpenAI, { APIConnectionError, APIConnectionTimeoutError, APIError, AuthenticationError, PermissionDeniedError, RateLimitError, } from "openai";
2
2
  import { DEFAULT_OPENAI_MODEL } from "./model-config.js";
3
3
  const VALIDATION_INPUT = "Tutti provider validation. Reply with the single word ok.";
4
4
  const VALIDATION_TIMEOUT_MS = 20_000;
@@ -58,8 +58,9 @@ function modelIdsFromResponse(response) {
58
58
  if (!isRecord(response) || !Array.isArray(response.data)) {
59
59
  return [];
60
60
  }
61
- return [...new Set(response.data.map(modelIdFromItem).filter((model) => model !== undefined))]
62
- .sort((left, right) => left.localeCompare(right));
61
+ return [
62
+ ...new Set(response.data.map(modelIdFromItem).filter((model) => model !== undefined)),
63
+ ].sort((left, right) => left.localeCompare(right));
63
64
  }
64
65
  function lowerErrorText(error) {
65
66
  if (error instanceof Error) {
@@ -158,10 +159,6 @@ function looksLikeQuotaOrBillingFailure(error) {
158
159
  message.includes("payment"));
159
160
  }
160
161
  function looksLikeModelUnavailable(error) {
161
- const status = apiErrorStatus(error);
162
- if (status === 404 || error instanceof NotFoundError) {
163
- return true;
164
- }
165
162
  const code = apiErrorCode(error);
166
163
  if (code === "model_not_found" || code === "model_not_available") {
167
164
  return true;
@@ -1,4 +1,5 @@
1
1
  import { type ProjectId, type RelayProjectRef } from "@tutti/shared/ids";
2
+ import { type ConfigureProjectOpenAiProviderResult } from "../../providers/openai/index.js";
2
3
  import { type FetchLike } from "../cli/host-runtime-endpoint.js";
3
4
  import { type RuntimeProjectRow } from "../cli/runtime-commands.js";
4
5
  import { type LocalConsoleInvocationContext } from "./invocation-context.js";
@@ -43,6 +44,10 @@ export declare class LocalConsoleProjectError extends Error {
43
44
  readonly statusCode: number;
44
45
  constructor(code: string, message: string, statusCode?: number);
45
46
  }
47
+ type LocalConsoleProviderValidationFailure = Extract<ConfigureProjectOpenAiProviderResult, {
48
+ kind: "validation_failed";
49
+ }>;
50
+ export declare function localConsoleProviderValidationMessage(result: LocalConsoleProviderValidationFailure): string;
46
51
  export declare class LocalConsoleProjectService {
47
52
  #private;
48
53
  constructor(options: {
@@ -75,4 +80,5 @@ export declare class LocalConsoleProjectService {
75
80
  message: string;
76
81
  }>;
77
82
  }
83
+ export {};
78
84
  //# sourceMappingURL=project-service.d.ts.map
@@ -23,6 +23,23 @@ export class LocalConsoleProjectError extends Error {
23
23
  this.name = "LocalConsoleProjectError";
24
24
  }
25
25
  }
26
+ export function localConsoleProviderValidationMessage(result) {
27
+ switch (result.reason) {
28
+ case "provider_auth_invalid":
29
+ return "The API key was rejected. Enter the raw bearer token and try again.";
30
+ case "provider_model_unavailable":
31
+ return "The selected model is not available through this Responses API endpoint.";
32
+ case "provider_quota_or_billing_required":
33
+ return "This Provider account does not have available quota or billing access.";
34
+ case "provider_rate_limited":
35
+ return "The Provider is rate limited. Wait a moment, then try again.";
36
+ case "provider_network_error":
37
+ if (result.diagnostic?.http_status === 404 || result.diagnostic?.http_status === 405) {
38
+ return "This Base URL does not expose an OpenAI-compatible POST /responses endpoint.";
39
+ }
40
+ return "Could not validate the OpenAI Responses API endpoint. Check the Base URL and try again.";
41
+ }
42
+ }
26
43
  function validateWorkspacePath(value) {
27
44
  const path = resolve(value.trim());
28
45
  if (value.trim() === "" || !existsSync(path) || !statSync(path).isDirectory()) {
@@ -274,7 +291,7 @@ export class LocalConsoleProjectService {
274
291
  defaultModel: input.provider.model,
275
292
  });
276
293
  if (configured.kind === "validation_failed") {
277
- throw new LocalConsoleProjectError(configured.reason, "Provider validation failed. Check the Base URL, API key, and selected model.");
294
+ throw new LocalConsoleProjectError(configured.reason, localConsoleProviderValidationMessage(configured));
278
295
  }
279
296
  }
280
297
  else if (preparation.provider_status !== "configured") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfey/tutti",
3
- "version": "0.1.44",
3
+ "version": "0.1.45",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",