@www.hyperlinks.space/program-kit 1.2.181818 → 7.8.3
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/.eas/workflows/create-development-builds.yml +21 -0
- package/.eas/workflows/create-draft.yml +15 -0
- package/.eas/workflows/deploy-to-production.yml +68 -0
- package/.env.example +19 -0
- package/.gitattributes +48 -0
- package/.gitignore +52 -0
- package/.nvmrc +1 -0
- package/.vercelignore +6 -0
- package/README.md +7 -2
- package/ai/openai.ts +202 -0
- package/ai/transmitter.ts +367 -0
- package/api/{base.ts → _base.ts} +1 -1
- package/api/wallet/_auth.ts +143 -0
- package/api/wallet/register.ts +151 -0
- package/api/wallet/status.ts +89 -0
- package/app/index.tsx +319 -5
- package/assets/images/PreviewImage.png +0 -0
- package/backlogs/medium_term_backlog.md +26 -0
- package/backlogs/short_term_backlog.md +42 -0
- package/database/start.ts +0 -1
- package/database/wallets.ts +266 -0
- package/eslint.config.cjs +10 -0
- package/fullREADME.md +142 -71
- package/index.js +3 -0
- package/npmReadMe.md +7 -2
- package/npmrc.example +1 -0
- package/package.json +7 -27
- package/polyfills/buffer.ts +9 -0
- package/research & docs/auth-and-centralized-encrypted-keys-plan.md +440 -0
- package/research & docs/github-gitlab-bidirectional-mirroring.md +154 -0
- package/research & docs/keys-retrieval-console-scripts.js +131 -0
- package/{docs/security_plan_raw.md → research & docs/security_plan_raw.md } +1 -1
- package/{docs/security_raw.md → research & docs/security_raw.md } +22 -13
- package/research & docs/storage-availability-console-script.js +152 -0
- package/research & docs/storage-lifetime.md +33 -0
- package/research & docs/telegram-raw-keys-cloud-storage-risks.md +31 -0
- package/{docs/wallets_hosting_architecture.md → research & docs/wallets_hosting_architecture.md } +147 -1
- package/scripts/test-api-base.ts +2 -2
- package/services/wallet/tonWallet.ts +73 -0
- package/telegram/post.ts +44 -8
- package/ui/components/GlobalBottomBar.tsx +447 -0
- package/ui/components/GlobalBottomBarWeb.tsx +362 -0
- package/ui/components/GlobalLogoBar.tsx +108 -0
- package/ui/components/GlobalLogoBarFallback.tsx +66 -0
- package/ui/components/GlobalLogoBarWithFallback.tsx +24 -0
- package/ui/components/HyperlinksSpaceLogo.tsx +29 -0
- package/ui/components/Telegram.tsx +677 -0
- package/ui/components/telegramWebApp.ts +359 -0
- package/ui/fonts.ts +12 -0
- package/ui/theme.ts +117 -0
- /package/{docs → research & docs}/ai_and_search_bar_input.md +0 -0
- /package/{docs → research & docs}/ai_bot_messages.md +0 -0
- /package/{docs → research & docs}/blue_bar_tackling.md +0 -0
- /package/{docs → research & docs}/bot_async_streaming.md +0 -0
- /package/{docs → research & docs}/build_and_install.md +0 -0
- /package/{docs → research & docs}/database_messages.md +0 -0
- /package/{docs → research & docs}/fonts.md +0 -0
- /package/{docs → research & docs}/npm-release.md +0 -0
- /package/{docs → research & docs}/releases.md +0 -0
- /package/{docs → research & docs}/releases_github_actions.md +0 -0
- /package/{docs → research & docs}/scalability.md +0 -0
- /package/{docs → research & docs}/timing_raw.md +0 -0
- /package/{docs → research & docs}/tma_logo_bar_jump_investigation.md +0 -0
- /package/{docs → research & docs}/update.md +0 -0
- /package/{docs → research & docs}/wallet_telegram_standalone_multichain_proposal.md +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: Create development builds
|
|
2
|
+
|
|
3
|
+
jobs:
|
|
4
|
+
android_development_build:
|
|
5
|
+
name: Build Android
|
|
6
|
+
type: build
|
|
7
|
+
params:
|
|
8
|
+
platform: android
|
|
9
|
+
profile: development
|
|
10
|
+
ios_device_development_build:
|
|
11
|
+
name: Build iOS device
|
|
12
|
+
type: build
|
|
13
|
+
params:
|
|
14
|
+
platform: ios
|
|
15
|
+
profile: development
|
|
16
|
+
ios_simulator_development_build:
|
|
17
|
+
name: Build iOS simulator
|
|
18
|
+
type: build
|
|
19
|
+
params:
|
|
20
|
+
platform: ios
|
|
21
|
+
profile: development-simulator
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
name: Deploy to production
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ['main']
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
fingerprint:
|
|
9
|
+
name: Fingerprint
|
|
10
|
+
type: fingerprint
|
|
11
|
+
get_android_build:
|
|
12
|
+
name: Check for existing android build
|
|
13
|
+
needs: [fingerprint]
|
|
14
|
+
type: get-build
|
|
15
|
+
params:
|
|
16
|
+
fingerprint_hash: ${{ needs.fingerprint.outputs.android_fingerprint_hash }}
|
|
17
|
+
profile: production
|
|
18
|
+
get_ios_build:
|
|
19
|
+
name: Check for existing ios build
|
|
20
|
+
needs: [fingerprint]
|
|
21
|
+
type: get-build
|
|
22
|
+
params:
|
|
23
|
+
fingerprint_hash: ${{ needs.fingerprint.outputs.ios_fingerprint_hash }}
|
|
24
|
+
profile: production
|
|
25
|
+
build_android:
|
|
26
|
+
name: Build Android
|
|
27
|
+
needs: [get_android_build]
|
|
28
|
+
if: ${{ !needs.get_android_build.outputs.build_id }}
|
|
29
|
+
type: build
|
|
30
|
+
params:
|
|
31
|
+
platform: android
|
|
32
|
+
profile: production
|
|
33
|
+
build_ios:
|
|
34
|
+
name: Build iOS
|
|
35
|
+
needs: [get_ios_build]
|
|
36
|
+
if: ${{ !needs.get_ios_build.outputs.build_id }}
|
|
37
|
+
type: build
|
|
38
|
+
params:
|
|
39
|
+
platform: ios
|
|
40
|
+
profile: production
|
|
41
|
+
submit_android_build:
|
|
42
|
+
name: Submit Android Build
|
|
43
|
+
needs: [build_android]
|
|
44
|
+
type: submit
|
|
45
|
+
params:
|
|
46
|
+
build_id: ${{ needs.build_android.outputs.build_id }}
|
|
47
|
+
submit_ios_build:
|
|
48
|
+
name: Submit iOS Build
|
|
49
|
+
needs: [build_ios]
|
|
50
|
+
type: submit
|
|
51
|
+
params:
|
|
52
|
+
build_id: ${{ needs.build_ios.outputs.build_id }}
|
|
53
|
+
publish_android_update:
|
|
54
|
+
name: Publish Android update
|
|
55
|
+
needs: [get_android_build]
|
|
56
|
+
if: ${{ needs.get_android_build.outputs.build_id }}
|
|
57
|
+
type: update
|
|
58
|
+
params:
|
|
59
|
+
branch: production
|
|
60
|
+
platform: android
|
|
61
|
+
publish_ios_update:
|
|
62
|
+
name: Publish iOS update
|
|
63
|
+
needs: [get_ios_build]
|
|
64
|
+
if: ${{ needs.get_ios_build.outputs.build_id }}
|
|
65
|
+
type: update
|
|
66
|
+
params:
|
|
67
|
+
branch: production
|
|
68
|
+
platform: ios
|
package/.env.example
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Copy to .env and fill in. Used by bot (npm run bot:local) and optionally by Expo.
|
|
2
|
+
# Do not commit .env.
|
|
3
|
+
|
|
4
|
+
# Telegram bot (required for local bot; optional for Expo app)
|
|
5
|
+
# Get token from @BotFather on Telegram
|
|
6
|
+
BOT_TOKEN=1234567890:ABCdefGHIjklMNOpqrsTUVwxyz-EXAMPLE
|
|
7
|
+
# Neon/Postgres connection string (from Neon dashboard)
|
|
8
|
+
DATABASE_URL=postgresql://user:password@ep-xxx-xxx.region.aws.neon.tech/neondb?sslmode=require
|
|
9
|
+
# OpenAI API key for /api/ai (required for AI responses). Get from https://platform.openai.com/api-keys
|
|
10
|
+
OPENAI=sk-proj-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
11
|
+
|
|
12
|
+
# Optional: base URL for calling APIs in dev (used by app and bot).
|
|
13
|
+
# Example for local Vercel dev: http://localhost:3000
|
|
14
|
+
# EXPO_PUBLIC_API_BASE_URL=http://localhost:3000
|
|
15
|
+
|
|
16
|
+
# Optional: Swap.Coffee API key for TON token info and routing.
|
|
17
|
+
# COFFEE=your-swap-coffee-api-key
|
|
18
|
+
# COFFEE_BASE_URL=https://api.swap.coffee
|
|
19
|
+
# COFFEE_TOKENS_BASE_URL=https://tokens.swap.coffee
|
package/.gitattributes
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Contribution-safe normalization policy:
|
|
2
|
+
# - Normalize all text files to LF in the repository.
|
|
3
|
+
# - Keep binary files untouched (no EOL, no text filters, no corrupted diffs).
|
|
4
|
+
# - Avoid noisy or garbled diffs from accidental encoding/filter changes.
|
|
5
|
+
|
|
6
|
+
# Default: treat files as text and normalize line endings.
|
|
7
|
+
* text=auto eol=lf
|
|
8
|
+
|
|
9
|
+
# Windows scripts should keep CRLF in working trees.
|
|
10
|
+
*.bat text eol=crlf
|
|
11
|
+
*.cmd text eol=crlf
|
|
12
|
+
*.ps1 text eol=crlf
|
|
13
|
+
|
|
14
|
+
# Common binary assets: never apply text normalization/filtering.
|
|
15
|
+
*.png binary
|
|
16
|
+
*.jpg binary
|
|
17
|
+
*.jpeg binary
|
|
18
|
+
*.gif binary
|
|
19
|
+
*.webp binary
|
|
20
|
+
*.ico binary
|
|
21
|
+
*.bmp binary
|
|
22
|
+
*.tif binary
|
|
23
|
+
*.tiff binary
|
|
24
|
+
*.pdf binary
|
|
25
|
+
*.zip binary
|
|
26
|
+
*.gz binary
|
|
27
|
+
*.7z binary
|
|
28
|
+
*.tar binary
|
|
29
|
+
*.tgz binary
|
|
30
|
+
*.rar binary
|
|
31
|
+
*.jar binary
|
|
32
|
+
*.woff binary
|
|
33
|
+
*.woff2 binary
|
|
34
|
+
*.ttf binary
|
|
35
|
+
*.eot binary
|
|
36
|
+
*.otf binary
|
|
37
|
+
*.mp3 binary
|
|
38
|
+
*.wav binary
|
|
39
|
+
*.mp4 binary
|
|
40
|
+
*.mov binary
|
|
41
|
+
*.avi binary
|
|
42
|
+
*.webm binary
|
|
43
|
+
|
|
44
|
+
# Git metadata and patches should always be text.
|
|
45
|
+
.gitattributes text eol=lf
|
|
46
|
+
.gitignore text eol=lf
|
|
47
|
+
*.patch text eol=lf
|
|
48
|
+
*.diff text eol=lf
|
package/.gitignore
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
|
|
2
|
+
|
|
3
|
+
# dependencies
|
|
4
|
+
node_modules/
|
|
5
|
+
|
|
6
|
+
# Expo
|
|
7
|
+
.expo/
|
|
8
|
+
dist/
|
|
9
|
+
web-build/
|
|
10
|
+
releases/
|
|
11
|
+
expo-env.d.ts
|
|
12
|
+
|
|
13
|
+
# Native
|
|
14
|
+
.kotlin/
|
|
15
|
+
*.orig.*
|
|
16
|
+
*.jks
|
|
17
|
+
*.p8
|
|
18
|
+
*.p12
|
|
19
|
+
*.key
|
|
20
|
+
*.mobileprovision
|
|
21
|
+
|
|
22
|
+
# Metro
|
|
23
|
+
.metro-health-check*
|
|
24
|
+
|
|
25
|
+
# local reference (do not commit)
|
|
26
|
+
build_ref.md
|
|
27
|
+
|
|
28
|
+
# generated when mirroring CI before npm pack (see README Milestone snapshot section)
|
|
29
|
+
fullREADME.md
|
|
30
|
+
|
|
31
|
+
# debug
|
|
32
|
+
npm-debug.*
|
|
33
|
+
yarn-debug.*
|
|
34
|
+
yarn-error.*
|
|
35
|
+
|
|
36
|
+
# macOS
|
|
37
|
+
.DS_Store
|
|
38
|
+
*.pem
|
|
39
|
+
|
|
40
|
+
# local env files (secrets; do not commit)
|
|
41
|
+
.env
|
|
42
|
+
.env*.local
|
|
43
|
+
|
|
44
|
+
# typescript
|
|
45
|
+
*.tsbuildinfo
|
|
46
|
+
|
|
47
|
+
app-example
|
|
48
|
+
|
|
49
|
+
# local scratch / temp
|
|
50
|
+
.tmp/
|
|
51
|
+
|
|
52
|
+
.vercel
|
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
20
|
package/.vercelignore
ADDED
package/README.md
CHANGED
|
@@ -33,12 +33,17 @@ registry and token.
|
|
|
33
33
|
|
|
34
34
|
## After Scaffold
|
|
35
35
|
|
|
36
|
+
Copy `npmrc.example` to `.npmrc` so installs match this repo (`legacy-peer-deps`; npm does not ship a real `.npmrc` in the tarball for security):
|
|
37
|
+
|
|
36
38
|
```bash
|
|
37
39
|
cd new-program
|
|
40
|
+
cp npmrc.example .npmrc
|
|
38
41
|
npm install
|
|
39
42
|
npm run start
|
|
40
43
|
```
|
|
41
44
|
|
|
45
|
+
If you prefer not to use a `.npmrc`, you can run **`npm install --legacy-peer-deps`** instead of the copy step.
|
|
46
|
+
|
|
42
47
|
Then open the project **`fullREADME.md`** for details (env vars, bot setup, build
|
|
43
48
|
and release commands).
|
|
44
49
|
|
|
@@ -49,5 +54,5 @@ and release commands).
|
|
|
49
54
|
|
|
50
55
|
## Notes
|
|
51
56
|
|
|
52
|
-
- Published
|
|
53
|
-
-
|
|
57
|
+
- Published from the repository root; the pack includes everything except patterns in [`.npmignore`](./.npmignore) (no `files` whitelist in `package.json`).
|
|
58
|
+
- `.npmrc` cannot be published on npm; `npmrc.example` is included so you can copy it locally.
|
package/ai/openai.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
|
|
3
|
+
export type AiMode = "chat" | "token_info";
|
|
4
|
+
|
|
5
|
+
export type ThreadContext = {
|
|
6
|
+
user_telegram: string;
|
|
7
|
+
thread_id: number;
|
|
8
|
+
type: "bot" | "app";
|
|
9
|
+
telegram_update_id?: number | null;
|
|
10
|
+
/** When true, skip claim insert (e.g. same handler retrying token_info -> chat); still use history and persist assistant. */
|
|
11
|
+
skipClaim?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type AiRequestBase = {
|
|
15
|
+
input: string;
|
|
16
|
+
userId?: string;
|
|
17
|
+
context?: Record<string, unknown>;
|
|
18
|
+
/** When set, AI layer persists user/assistant and uses thread history for chat. */
|
|
19
|
+
threadContext?: ThreadContext;
|
|
20
|
+
/** Optional instructions for the model (e.g. length limit); passed to OpenAI native `instructions` field. */
|
|
21
|
+
instructions?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type AiResponseBase = {
|
|
25
|
+
ok: boolean;
|
|
26
|
+
provider: "openai";
|
|
27
|
+
output_text?: string;
|
|
28
|
+
error?: string;
|
|
29
|
+
mode: AiMode;
|
|
30
|
+
/** True when claim insert failed (another instance or duplicate); caller should not send. */
|
|
31
|
+
skipped?: boolean;
|
|
32
|
+
usage?: {
|
|
33
|
+
prompt_tokens?: number;
|
|
34
|
+
completion_tokens?: number;
|
|
35
|
+
total_tokens?: number;
|
|
36
|
+
};
|
|
37
|
+
meta?: Record<string, unknown>;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const OPENAI = process.env.OPENAI?.trim() || "";
|
|
41
|
+
|
|
42
|
+
const client = OPENAI ? new OpenAI({ apiKey: OPENAI }) : null;
|
|
43
|
+
|
|
44
|
+
export async function callOpenAiChat(
|
|
45
|
+
mode: AiMode,
|
|
46
|
+
params: AiRequestBase,
|
|
47
|
+
): Promise<AiResponseBase> {
|
|
48
|
+
if (!client) {
|
|
49
|
+
return {
|
|
50
|
+
ok: false,
|
|
51
|
+
provider: "openai",
|
|
52
|
+
mode,
|
|
53
|
+
error: "OPENAI env is not configured on the server.",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const trimmed = params.input?.trim();
|
|
58
|
+
if (!trimmed) {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
provider: "openai",
|
|
62
|
+
mode,
|
|
63
|
+
error: "input is required.",
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const prefix =
|
|
68
|
+
mode === "token_info"
|
|
69
|
+
? "You are a blockchain and token analyst. Answer clearly and briefly.\n\n"
|
|
70
|
+
: "";
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const response = await client.responses.create({
|
|
74
|
+
model: "gpt-5.2",
|
|
75
|
+
...(params.instructions ? { instructions: params.instructions } : {}),
|
|
76
|
+
input: `${prefix}${trimmed}`,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
ok: true,
|
|
81
|
+
provider: "openai",
|
|
82
|
+
mode,
|
|
83
|
+
output_text: (response as any).output_text ?? undefined,
|
|
84
|
+
usage: (response as any).usage ?? undefined,
|
|
85
|
+
};
|
|
86
|
+
} catch (e: any) {
|
|
87
|
+
const message =
|
|
88
|
+
e?.message ?? "Failed to call OpenAI. Check OPENAI env and network.";
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
provider: "openai",
|
|
92
|
+
mode,
|
|
93
|
+
error: message,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Call OpenAI with streaming; onDelta(textSoFar) is called for each chunk. Returns final response. */
|
|
99
|
+
export async function callOpenAiChatStream(
|
|
100
|
+
mode: AiMode,
|
|
101
|
+
params: AiRequestBase,
|
|
102
|
+
onDelta: (text: string) => void | Promise<void>,
|
|
103
|
+
opts?: { isCancelled?: () => boolean; getAbortSignal?: () => Promise<boolean> },
|
|
104
|
+
): Promise<AiResponseBase> {
|
|
105
|
+
if (!client) {
|
|
106
|
+
return {
|
|
107
|
+
ok: false,
|
|
108
|
+
provider: "openai",
|
|
109
|
+
mode,
|
|
110
|
+
error: "OPENAI env is not configured on the server.",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const trimmed = params.input?.trim();
|
|
115
|
+
if (!trimmed) {
|
|
116
|
+
return {
|
|
117
|
+
ok: false,
|
|
118
|
+
provider: "openai",
|
|
119
|
+
mode,
|
|
120
|
+
error: "input is required.",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const prefix =
|
|
125
|
+
mode === "token_info"
|
|
126
|
+
? "You are a blockchain and token analyst. Answer clearly and briefly.\n\n"
|
|
127
|
+
: "";
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const stream = client.responses.stream({
|
|
131
|
+
model: "gpt-5.2",
|
|
132
|
+
...(params.instructions ? { instructions: params.instructions } : {}),
|
|
133
|
+
input: `${prefix}${trimmed}`,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
stream.on("response.output_text.delta", async (event: { snapshot?: string }) => {
|
|
137
|
+
if (opts?.isCancelled && opts.isCancelled()) {
|
|
138
|
+
try {
|
|
139
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
140
|
+
(stream as any)?.abort?.();
|
|
141
|
+
} catch {
|
|
142
|
+
/* ignore */
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (opts?.getAbortSignal && (await opts.getAbortSignal())) {
|
|
147
|
+
try {
|
|
148
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
149
|
+
(stream as any)?.abort?.();
|
|
150
|
+
} catch {
|
|
151
|
+
/* ignore */
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const text = event?.snapshot ?? "";
|
|
156
|
+
if (text.length > 0) void Promise.resolve(onDelta(text));
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const response = await stream.finalResponse();
|
|
160
|
+
const r = response as any;
|
|
161
|
+
let output_text = r.output_text;
|
|
162
|
+
if (output_text == null || String(output_text).trim() === "") {
|
|
163
|
+
const parts: string[] = [];
|
|
164
|
+
for (const item of r.output ?? []) {
|
|
165
|
+
if (item?.type === "message" && Array.isArray(item.content)) {
|
|
166
|
+
for (const content of item.content) {
|
|
167
|
+
if (content?.type === "output_text" && typeof content.text === "string") {
|
|
168
|
+
parts.push(content.text);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
output_text = parts.join("");
|
|
174
|
+
}
|
|
175
|
+
if (output_text == null || String(output_text).trim() === "") {
|
|
176
|
+
return {
|
|
177
|
+
ok: false,
|
|
178
|
+
provider: "openai",
|
|
179
|
+
mode,
|
|
180
|
+
error: "OpenAI returned no text.",
|
|
181
|
+
usage: r.usage ?? undefined,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
ok: true,
|
|
186
|
+
provider: "openai",
|
|
187
|
+
mode,
|
|
188
|
+
output_text,
|
|
189
|
+
usage: r.usage ?? undefined,
|
|
190
|
+
};
|
|
191
|
+
} catch (e: any) {
|
|
192
|
+
const message =
|
|
193
|
+
(e && typeof e === "object" && "message" in e ? (e as Error).message : null) ??
|
|
194
|
+
(e != null ? String(e) : "Failed to call OpenAI. Check OPENAI env and network.");
|
|
195
|
+
return {
|
|
196
|
+
ok: false,
|
|
197
|
+
provider: "openai",
|
|
198
|
+
mode,
|
|
199
|
+
error: message,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
}
|