@victor-software-house/pi-multicodex 2.0.6 → 2.0.8
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 +5 -4
- package/abort-utils.ts +9 -24
- package/account-manager.ts +28 -17
- package/auth.ts +2 -3
- package/commands.ts +4 -8
- package/package.json +8 -5
- package/provider.ts +17 -31
- package/status.ts +8 -19
- package/storage.ts +2 -7
- package/stream-wrapper.ts +10 -51
- package/assets/multicodex.png +0 -0
- /package/assets/{screenshots/multicodex-footer-settings.png → multicodex-footer-settings.png} +0 -0
- /package/assets/{screenshots/multicodex-main.png → multicodex-main.png} +0 -0
- /package/assets/{screenshots/multicodex-remove-confirm.png → multicodex-remove-confirm.png} +0 -0
- /package/assets/{screenshots/multicodex-use-picker.png → multicodex-use-picker.png} +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @victor-software-house/pi-multicodex
|
|
2
2
|
|
|
3
|
-

|
|
4
4
|
|
|
5
5
|
MultiCodex is a [pi](https://github.com/badlogic/pi-mono) extension that manages multiple ChatGPT Codex accounts and rotates between them automatically when you hit quota limits.
|
|
6
6
|
|
|
@@ -54,14 +54,14 @@ Commands that do not need a UI panel (`show`, `verify`, `path`, `reset`, `help`)
|
|
|
54
54
|
|
|
55
55
|
The `/multicodex use` picker lets you select, add, and remove accounts in one place.
|
|
56
56
|
|
|
57
|
-

|
|
58
58
|
|
|
59
59
|
- **Enter** activates the highlighted account.
|
|
60
60
|
- **Backspace** removes it (after confirmation).
|
|
61
61
|
|
|
62
62
|
When you remove an active account, MultiCodex switches to the next available one automatically.
|
|
63
63
|
|
|
64
|
-

|
|
65
65
|
|
|
66
66
|
## Usage footer
|
|
67
67
|
|
|
@@ -69,7 +69,7 @@ MultiCodex adds a live footer to your session showing the active account, 5-hour
|
|
|
69
69
|
|
|
70
70
|
You can customize which fields appear and their ordering with `/multicodex footer`.
|
|
71
71
|
|
|
72
|
-

|
|
73
73
|
|
|
74
74
|
## What it does under the hood
|
|
75
75
|
|
|
@@ -78,6 +78,7 @@ You can customize which fields appear and their ordering with `/multicodex foote
|
|
|
78
78
|
- **Token refresh.** OAuth tokens are refreshed before expiry so requests do not fail due to stale credentials.
|
|
79
79
|
- **Usage tracking.** Usage data is fetched from the Codex API and cached for 5 minutes per account. The footer renders cached data immediately and refreshes in the background.
|
|
80
80
|
- **Quota cooldown.** When an account is exhausted, it stays on cooldown until its next known reset time (or 1 hour if the reset time is unknown).
|
|
81
|
+
- **Shared utility seams.** Provider mirroring, stream primitives, and `~/.pi/agent/*` path helpers are shared with `pi-credential-vault` through `@victor-software-house/pi-provider-utils`. MultiCodex still owns account storage, token policy, footer behavior, and command UX.
|
|
81
82
|
|
|
82
83
|
## Local development
|
|
83
84
|
|
package/abort-utils.ts
CHANGED
|
@@ -1,24 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
signal?.addEventListener("abort", () => controller.abort(), { once: true });
|
|
11
|
-
return controller;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function createTimeoutController(
|
|
15
|
-
signal: AbortSignal | undefined,
|
|
16
|
-
timeoutMs: number,
|
|
17
|
-
): { controller: AbortController; clear: () => void } {
|
|
18
|
-
const controller = createLinkedAbortController(signal);
|
|
19
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
20
|
-
return {
|
|
21
|
-
controller,
|
|
22
|
-
clear: () => clearTimeout(timeout),
|
|
23
|
-
};
|
|
24
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Re-export abort controller helpers from the shared package.
|
|
3
|
+
*
|
|
4
|
+
* Existing imports within this package continue to work unchanged.
|
|
5
|
+
*/
|
|
6
|
+
export {
|
|
7
|
+
createLinkedAbortController,
|
|
8
|
+
createTimeoutController,
|
|
9
|
+
} from "pi-provider-utils/streams";
|
package/account-manager.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
type OAuthCredentials,
|
|
3
3
|
refreshOpenAICodexToken,
|
|
4
4
|
} from "@mariozechner/pi-ai/oauth";
|
|
5
|
+
import { normalizeUnknownError } from "pi-provider-utils/streams";
|
|
5
6
|
import { loadImportedOpenAICodexAuth } from "./auth";
|
|
6
7
|
import { isAccountAvailable, pickBestAccount } from "./selection";
|
|
7
8
|
import {
|
|
@@ -20,14 +21,10 @@ const QUOTA_COOLDOWN_MS = 60 * 60 * 1000;
|
|
|
20
21
|
type WarningHandler = (message: string) => void;
|
|
21
22
|
type StateChangeHandler = () => void;
|
|
22
23
|
|
|
23
|
-
function getErrorMessage(error: unknown): string {
|
|
24
|
-
if (error instanceof Error) return error.message;
|
|
25
|
-
return typeof error === "string" ? error : JSON.stringify(error);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
24
|
export class AccountManager {
|
|
29
25
|
private data: StorageData;
|
|
30
26
|
private usageCache = new Map<string, CodexUsageSnapshot>();
|
|
27
|
+
private refreshPromises = new Map<string, Promise<string>>();
|
|
31
28
|
private warningHandler?: WarningHandler;
|
|
32
29
|
private manualEmail?: string;
|
|
33
30
|
private stateChangeHandlers = new Set<StateChangeHandler>();
|
|
@@ -258,7 +255,7 @@ export class AccountManager {
|
|
|
258
255
|
return usage;
|
|
259
256
|
} catch (error) {
|
|
260
257
|
this.warningHandler?.(
|
|
261
|
-
`Multicodex: failed to fetch usage for ${account.email}: ${
|
|
258
|
+
`Multicodex: failed to fetch usage for ${account.email}: ${normalizeUnknownError(
|
|
262
259
|
error,
|
|
263
260
|
)}`,
|
|
264
261
|
);
|
|
@@ -346,17 +343,31 @@ export class AccountManager {
|
|
|
346
343
|
return account.accessToken;
|
|
347
344
|
}
|
|
348
345
|
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
account.expiresAt = result.expires;
|
|
353
|
-
const accountId =
|
|
354
|
-
typeof result.accountId === "string" ? result.accountId : undefined;
|
|
355
|
-
if (accountId) {
|
|
356
|
-
account.accountId = accountId;
|
|
346
|
+
const inflight = this.refreshPromises.get(account.email);
|
|
347
|
+
if (inflight) {
|
|
348
|
+
return inflight;
|
|
357
349
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
350
|
+
|
|
351
|
+
const promise = (async () => {
|
|
352
|
+
try {
|
|
353
|
+
const result = await refreshOpenAICodexToken(account.refreshToken);
|
|
354
|
+
account.accessToken = result.access;
|
|
355
|
+
account.refreshToken = result.refresh;
|
|
356
|
+
account.expiresAt = result.expires;
|
|
357
|
+
const accountId =
|
|
358
|
+
typeof result.accountId === "string" ? result.accountId : undefined;
|
|
359
|
+
if (accountId) {
|
|
360
|
+
account.accountId = accountId;
|
|
361
|
+
}
|
|
362
|
+
this.save();
|
|
363
|
+
this.notifyStateChanged();
|
|
364
|
+
return account.accessToken;
|
|
365
|
+
} finally {
|
|
366
|
+
this.refreshPromises.delete(account.email);
|
|
367
|
+
}
|
|
368
|
+
})();
|
|
369
|
+
|
|
370
|
+
this.refreshPromises.set(account.email, promise);
|
|
371
|
+
return promise;
|
|
361
372
|
}
|
|
362
373
|
}
|
package/auth.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { promises as fs } from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
2
|
import type { OAuthCredentials } from "@mariozechner/pi-ai/oauth";
|
|
3
|
+
import { getAgentAuthPath } from "pi-provider-utils/agent-paths";
|
|
5
4
|
|
|
6
|
-
const AUTH_FILE =
|
|
5
|
+
const AUTH_FILE = getAgentAuthPath();
|
|
7
6
|
const IMPORTED_ACCOUNT_PREFIX = "OpenAI Codex";
|
|
8
7
|
|
|
9
8
|
interface AuthEntry {
|
package/commands.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { promises as fs, constants as fsConstants } from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { loginOpenAICodex } from "@mariozechner/pi-ai/oauth";
|
|
5
4
|
import type {
|
|
@@ -15,13 +14,15 @@ import {
|
|
|
15
14
|
SelectList,
|
|
16
15
|
Text,
|
|
17
16
|
} from "@mariozechner/pi-tui";
|
|
17
|
+
import { getAgentSettingsPath } from "pi-provider-utils/agent-paths";
|
|
18
|
+
import { normalizeUnknownError } from "pi-provider-utils/streams";
|
|
18
19
|
import type { AccountManager } from "./account-manager";
|
|
19
20
|
import { openLoginInBrowser } from "./browser";
|
|
20
21
|
import type { createUsageStatusController } from "./status";
|
|
21
22
|
import { STORAGE_FILE } from "./storage";
|
|
22
23
|
import { formatResetAt, isUsageUntouched } from "./usage";
|
|
23
24
|
|
|
24
|
-
const SETTINGS_FILE =
|
|
25
|
+
const SETTINGS_FILE = getAgentSettingsPath();
|
|
25
26
|
const NO_ACCOUNTS_MESSAGE =
|
|
26
27
|
"No managed accounts found. Use /multicodex use <identifier> first.";
|
|
27
28
|
const HELP_TEXT =
|
|
@@ -46,11 +47,6 @@ type AccountPanelResult =
|
|
|
46
47
|
| { action: "remove"; email: string }
|
|
47
48
|
| undefined;
|
|
48
49
|
|
|
49
|
-
function getErrorMessage(error: unknown): string {
|
|
50
|
-
if (error instanceof Error) return error.message;
|
|
51
|
-
return typeof error === "string" ? error : JSON.stringify(error);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
50
|
function toAutocompleteItems(values: readonly string[]): AutocompleteItem[] {
|
|
55
51
|
return values.map((value) => ({ value, label: value }));
|
|
56
52
|
}
|
|
@@ -202,7 +198,7 @@ async function loginAndActivateAccount(
|
|
|
202
198
|
ctx.ui.notify(`Now using ${identifier}`, "info");
|
|
203
199
|
return true;
|
|
204
200
|
} catch (error) {
|
|
205
|
-
ctx.ui.notify(`Login failed: ${
|
|
201
|
+
ctx.ui.notify(`Login failed: ${normalizeUnknownError(error)}`, "error");
|
|
206
202
|
return false;
|
|
207
203
|
}
|
|
208
204
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@victor-software-house/pi-multicodex",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.8",
|
|
4
4
|
"description": "Codex account rotation extension for pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"extensions": [
|
|
19
19
|
"./index.ts"
|
|
20
20
|
],
|
|
21
|
-
"image": "https://raw.githubusercontent.com/victor-software-house/pi-multicodex/main/assets/multicodex.png"
|
|
21
|
+
"image": "https://raw.githubusercontent.com/victor-software-house/pi-multicodex/main/assets/multicodex-main.png"
|
|
22
22
|
},
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
|
@@ -78,9 +78,9 @@
|
|
|
78
78
|
"@biomejs/biome": "^2.4.7",
|
|
79
79
|
"@commitlint/cli": "^20.4.4",
|
|
80
80
|
"@commitlint/config-conventional": "^20.4.4",
|
|
81
|
-
"@mariozechner/pi-ai": "^0.
|
|
82
|
-
"@mariozechner/pi-coding-agent": "^0.
|
|
83
|
-
"@mariozechner/pi-tui": "^0.
|
|
81
|
+
"@mariozechner/pi-ai": "^0.63.1",
|
|
82
|
+
"@mariozechner/pi-coding-agent": "^0.63.1",
|
|
83
|
+
"@mariozechner/pi-tui": "^0.63.1",
|
|
84
84
|
"@semantic-release/changelog": "^6.0.3",
|
|
85
85
|
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
86
86
|
"@semantic-release/git": "^10.0.1",
|
|
@@ -95,5 +95,8 @@
|
|
|
95
95
|
},
|
|
96
96
|
"engines": {
|
|
97
97
|
"node": "24.14.0"
|
|
98
|
+
},
|
|
99
|
+
"dependencies": {
|
|
100
|
+
"pi-provider-utils": "^0.0.0"
|
|
98
101
|
}
|
|
99
102
|
}
|
package/provider.ts
CHANGED
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
type AssistantMessageEventStream,
|
|
4
|
-
type Context,
|
|
5
|
-
getApiProvider,
|
|
6
|
-
getModels,
|
|
7
|
-
type Model,
|
|
8
|
-
type SimpleStreamOptions,
|
|
9
|
-
} from "@mariozechner/pi-ai";
|
|
1
|
+
import { getApiProvider } from "@mariozechner/pi-ai";
|
|
2
|
+
import { mirrorProvider } from "pi-provider-utils/providers";
|
|
10
3
|
import type { AccountManager } from "./account-manager";
|
|
11
4
|
import { createStreamWrapper } from "./stream-wrapper";
|
|
12
5
|
|
|
@@ -31,32 +24,25 @@ export function getOpenAICodexMirror(): {
|
|
|
31
24
|
baseUrl: string;
|
|
32
25
|
models: ProviderModelDef[];
|
|
33
26
|
} {
|
|
34
|
-
const
|
|
27
|
+
const mirror = mirrorProvider("openai-codex");
|
|
28
|
+
if (!mirror) {
|
|
29
|
+
return { baseUrl: "https://chatgpt.com/backend-api", models: [] };
|
|
30
|
+
}
|
|
35
31
|
return {
|
|
36
|
-
baseUrl:
|
|
37
|
-
models:
|
|
38
|
-
id:
|
|
39
|
-
name:
|
|
40
|
-
reasoning:
|
|
41
|
-
input:
|
|
42
|
-
cost:
|
|
43
|
-
contextWindow:
|
|
44
|
-
maxTokens:
|
|
32
|
+
baseUrl: mirror.baseUrl,
|
|
33
|
+
models: mirror.models.map((m) => ({
|
|
34
|
+
id: m.id,
|
|
35
|
+
name: m.name,
|
|
36
|
+
reasoning: m.reasoning,
|
|
37
|
+
input: [...m.input],
|
|
38
|
+
cost: { ...m.cost },
|
|
39
|
+
contextWindow: m.contextWindow,
|
|
40
|
+
maxTokens: m.maxTokens,
|
|
45
41
|
})),
|
|
46
42
|
};
|
|
47
43
|
}
|
|
48
44
|
|
|
49
|
-
export function buildMulticodexProviderConfig(accountManager: AccountManager)
|
|
50
|
-
baseUrl: string;
|
|
51
|
-
apiKey: string;
|
|
52
|
-
api: "openai-codex-responses";
|
|
53
|
-
streamSimple: (
|
|
54
|
-
model: Model<Api>,
|
|
55
|
-
context: Context,
|
|
56
|
-
options?: SimpleStreamOptions,
|
|
57
|
-
) => AssistantMessageEventStream;
|
|
58
|
-
models: ProviderModelDef[];
|
|
59
|
-
} {
|
|
45
|
+
export function buildMulticodexProviderConfig(accountManager: AccountManager) {
|
|
60
46
|
const mirror = getOpenAICodexMirror();
|
|
61
47
|
const baseProvider = getApiProvider("openai-codex-responses");
|
|
62
48
|
if (!baseProvider) {
|
|
@@ -68,7 +54,7 @@ export function buildMulticodexProviderConfig(accountManager: AccountManager): {
|
|
|
68
54
|
return {
|
|
69
55
|
baseUrl: mirror.baseUrl,
|
|
70
56
|
apiKey: "managed-by-extension",
|
|
71
|
-
api: "openai-codex-responses",
|
|
57
|
+
api: "openai-codex-responses" as const,
|
|
72
58
|
streamSimple: createStreamWrapper(accountManager, baseProvider),
|
|
73
59
|
models: mirror.models,
|
|
74
60
|
};
|
package/status.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { promises as fs } from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
1
|
import type { Api, Model } from "@mariozechner/pi-ai";
|
|
5
2
|
import type {
|
|
6
3
|
ExtensionCommandContext,
|
|
@@ -13,13 +10,18 @@ import {
|
|
|
13
10
|
SettingsList,
|
|
14
11
|
Text,
|
|
15
12
|
} from "@mariozechner/pi-tui";
|
|
13
|
+
import {
|
|
14
|
+
getAgentSettingsPath,
|
|
15
|
+
readJsonObjectFileAsync,
|
|
16
|
+
writeJsonObjectFileAsync,
|
|
17
|
+
} from "pi-provider-utils/agent-paths";
|
|
16
18
|
import type { AccountManager } from "./account-manager";
|
|
17
19
|
import { PROVIDER_ID } from "./provider";
|
|
18
20
|
import type { CodexUsageSnapshot } from "./usage";
|
|
19
21
|
|
|
20
22
|
const STATUS_KEY = "multicodex-usage";
|
|
21
23
|
const SETTINGS_KEY = "pi-multicodex";
|
|
22
|
-
const SETTINGS_FILE =
|
|
24
|
+
const SETTINGS_FILE = getAgentSettingsPath();
|
|
23
25
|
const REFRESH_INTERVAL_MS = 60_000;
|
|
24
26
|
const MODEL_SELECT_REFRESH_DEBOUNCE_MS = 250;
|
|
25
27
|
const UNKNOWN_PERCENT = "--";
|
|
@@ -90,26 +92,13 @@ function normalizePreferences(value: unknown): FooterPreferences {
|
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
async function readSettingsFile(): Promise<Record<string, unknown>> {
|
|
93
|
-
|
|
94
|
-
const raw = await fs.readFile(SETTINGS_FILE, "utf8");
|
|
95
|
-
const parsed = JSON.parse(raw) as unknown;
|
|
96
|
-
return asObject(parsed) ?? {};
|
|
97
|
-
} catch (error) {
|
|
98
|
-
const withCode = error as Error & { code?: string };
|
|
99
|
-
if (withCode.code === "ENOENT") return {};
|
|
100
|
-
throw error;
|
|
101
|
-
}
|
|
95
|
+
return readJsonObjectFileAsync(SETTINGS_FILE);
|
|
102
96
|
}
|
|
103
97
|
|
|
104
98
|
async function writeSettingsFile(
|
|
105
99
|
settings: Record<string, unknown>,
|
|
106
100
|
): Promise<void> {
|
|
107
|
-
await
|
|
108
|
-
await fs.writeFile(
|
|
109
|
-
SETTINGS_FILE,
|
|
110
|
-
`${JSON.stringify(settings, null, 2)}\n`,
|
|
111
|
-
"utf8",
|
|
112
|
-
);
|
|
101
|
+
await writeJsonObjectFileAsync(SETTINGS_FILE, settings);
|
|
113
102
|
}
|
|
114
103
|
|
|
115
104
|
export async function loadFooterPreferences(): Promise<FooterPreferences> {
|
package/storage.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
import * as os from "node:os";
|
|
3
2
|
import * as path from "node:path";
|
|
3
|
+
import { getAgentPath } from "pi-provider-utils/agent-paths";
|
|
4
4
|
|
|
5
5
|
export interface Account {
|
|
6
6
|
email: string;
|
|
@@ -19,12 +19,7 @@ export interface StorageData {
|
|
|
19
19
|
activeEmail?: string;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
export const STORAGE_FILE =
|
|
23
|
-
os.homedir(),
|
|
24
|
-
".pi",
|
|
25
|
-
"agent",
|
|
26
|
-
"codex-accounts.json",
|
|
27
|
-
);
|
|
22
|
+
export const STORAGE_FILE = getAgentPath("codex-accounts.json");
|
|
28
23
|
|
|
29
24
|
export function loadStorage(): StorageData {
|
|
30
25
|
try {
|
package/stream-wrapper.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type Api,
|
|
3
|
-
type AssistantMessage,
|
|
4
3
|
type AssistantMessageEvent,
|
|
5
4
|
type AssistantMessageEventStream,
|
|
6
5
|
type Context,
|
|
@@ -8,7 +7,12 @@ import {
|
|
|
8
7
|
type Model,
|
|
9
8
|
type SimpleStreamOptions,
|
|
10
9
|
} from "@mariozechner/pi-ai";
|
|
11
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
createErrorAssistantMessage,
|
|
12
|
+
createLinkedAbortController,
|
|
13
|
+
normalizeUnknownError,
|
|
14
|
+
rewriteProviderOnEvent,
|
|
15
|
+
} from "pi-provider-utils/streams";
|
|
12
16
|
import type { AccountManager } from "./account-manager";
|
|
13
17
|
import { isQuotaErrorMessage } from "./quota";
|
|
14
18
|
|
|
@@ -22,51 +26,6 @@ type ApiProviderRef = {
|
|
|
22
26
|
) => AssistantMessageEventStream;
|
|
23
27
|
};
|
|
24
28
|
|
|
25
|
-
function withProvider(
|
|
26
|
-
event: AssistantMessageEvent,
|
|
27
|
-
provider: string,
|
|
28
|
-
): AssistantMessageEvent {
|
|
29
|
-
if ("partial" in event) {
|
|
30
|
-
return { ...event, partial: { ...event.partial, provider } };
|
|
31
|
-
}
|
|
32
|
-
if (event.type === "done") {
|
|
33
|
-
return { ...event, message: { ...event.message, provider } };
|
|
34
|
-
}
|
|
35
|
-
if (event.type === "error") {
|
|
36
|
-
return { ...event, error: { ...event.error, provider } };
|
|
37
|
-
}
|
|
38
|
-
return event;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function createErrorAssistantMessage(
|
|
42
|
-
model: Model<Api>,
|
|
43
|
-
message: string,
|
|
44
|
-
): AssistantMessage {
|
|
45
|
-
return {
|
|
46
|
-
role: "assistant",
|
|
47
|
-
content: [],
|
|
48
|
-
api: model.api,
|
|
49
|
-
provider: model.provider,
|
|
50
|
-
model: model.id,
|
|
51
|
-
usage: {
|
|
52
|
-
input: 0,
|
|
53
|
-
output: 0,
|
|
54
|
-
cacheRead: 0,
|
|
55
|
-
cacheWrite: 0,
|
|
56
|
-
totalTokens: 0,
|
|
57
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
58
|
-
},
|
|
59
|
-
stopReason: "error",
|
|
60
|
-
errorMessage: message,
|
|
61
|
-
timestamp: Date.now(),
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function getErrorMessage(error: unknown): string {
|
|
66
|
-
if (error instanceof Error) return error.message;
|
|
67
|
-
return typeof error === "string" ? error : JSON.stringify(error);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
29
|
export function createStreamWrapper(
|
|
71
30
|
accountManager: AccountManager,
|
|
72
31
|
baseProvider: ApiProviderRef,
|
|
@@ -151,13 +110,13 @@ export function createStreamWrapper(
|
|
|
151
110
|
break;
|
|
152
111
|
}
|
|
153
112
|
|
|
154
|
-
stream.push(
|
|
113
|
+
stream.push(rewriteProviderOnEvent(event, model.provider));
|
|
155
114
|
stream.end();
|
|
156
115
|
return;
|
|
157
116
|
}
|
|
158
117
|
|
|
159
118
|
forwardedAny = true;
|
|
160
|
-
stream.push(
|
|
119
|
+
stream.push(rewriteProviderOnEvent(event, model.provider));
|
|
161
120
|
|
|
162
121
|
if (event.type === "done") {
|
|
163
122
|
stream.end();
|
|
@@ -173,7 +132,7 @@ export function createStreamWrapper(
|
|
|
173
132
|
return;
|
|
174
133
|
}
|
|
175
134
|
} catch (error) {
|
|
176
|
-
const message =
|
|
135
|
+
const message = normalizeUnknownError(error);
|
|
177
136
|
const errorEvent: AssistantMessageEvent = {
|
|
178
137
|
type: "error",
|
|
179
138
|
reason: "error",
|
|
@@ -182,7 +141,7 @@ export function createStreamWrapper(
|
|
|
182
141
|
`Multicodex failed: ${message}`,
|
|
183
142
|
),
|
|
184
143
|
};
|
|
185
|
-
stream.push(
|
|
144
|
+
stream.push(rewriteProviderOnEvent(errorEvent, model.provider));
|
|
186
145
|
stream.end();
|
|
187
146
|
}
|
|
188
147
|
})();
|
package/assets/multicodex.png
DELETED
|
Binary file
|
/package/assets/{screenshots/multicodex-footer-settings.png → multicodex-footer-settings.png}
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|