@oh-my-pi/pi-coding-agent 16.1.2 → 16.1.4
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/CHANGELOG.md +44 -1
- package/dist/cli.js +2990 -2991
- package/dist/types/config/model-resolver.d.ts +3 -3
- package/dist/types/mnemopi/embed-client.d.ts +70 -0
- package/dist/types/mnemopi/embed-protocol.d.ts +52 -0
- package/dist/types/mnemopi/embed-worker.d.ts +12 -0
- package/dist/types/mnemopi/state.d.ts +9 -1
- package/dist/types/modes/components/cache-invalidation-marker.d.ts +23 -10
- package/dist/types/modes/components/status-line/component.d.ts +2 -3
- package/dist/types/sdk.d.ts +12 -0
- package/dist/types/session/agent-session.d.ts +2 -0
- package/dist/types/session/agent-storage.d.ts +2 -0
- package/dist/types/session/auth-broker-config.d.ts +3 -2
- package/dist/types/session/history-storage.d.ts +1 -1
- package/dist/types/session/tool-choice-queue.d.ts +2 -0
- package/dist/types/tools/image-gen.d.ts +2 -2
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tui/hyperlink.d.ts +3 -2
- package/dist/types/utils/image-loading.d.ts +1 -1
- package/dist/types/utils/ipc.d.ts +22 -0
- package/dist/types/web/search/providers/perplexity-auth.d.ts +37 -0
- package/package.json +12 -12
- package/src/cli/bench-cli.ts +33 -2
- package/src/cli/dry-balance-cli.ts +4 -2
- package/src/cli.ts +8 -0
- package/src/commands/token.ts +52 -33
- package/src/config/append-only-context-mode.ts +45 -0
- package/src/config/model-discovery.ts +3 -0
- package/src/config/model-registry.ts +21 -3
- package/src/config/model-resolver.ts +31 -8
- package/src/discovery/builtin-rules/ts-no-return-type.md +0 -1
- package/src/extensibility/plugins/manager.ts +82 -22
- package/src/lsp/client.ts +24 -0
- package/src/mnemopi/backend.ts +49 -3
- package/src/mnemopi/embed-client.ts +401 -0
- package/src/mnemopi/embed-protocol.ts +35 -0
- package/src/mnemopi/embed-worker.ts +113 -0
- package/src/mnemopi/state.ts +29 -1
- package/src/modes/components/cache-invalidation-marker.ts +31 -15
- package/src/modes/components/custom-editor.test.ts +4 -3
- package/src/modes/components/custom-editor.ts +1 -1
- package/src/modes/components/model-selector.ts +2 -2
- package/src/modes/components/status-line/component.ts +64 -18
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/event-controller.ts +8 -0
- package/src/modes/controllers/selector-controller.ts +2 -2
- package/src/modes/theme/theme.ts +69 -0
- package/src/sdk.ts +37 -0
- package/src/session/agent-session.ts +13 -0
- package/src/session/agent-storage.ts +14 -0
- package/src/session/auth-broker-config.ts +2 -1
- package/src/session/history-storage.ts +13 -1
- package/src/session/tool-choice-queue.ts +6 -0
- package/src/stt/asr-client.ts +2 -7
- package/src/tiny/title-client.ts +2 -7
- package/src/tools/image-gen.ts +4 -8
- package/src/tools/index.ts +2 -0
- package/src/tools/render-utils.ts +4 -1
- package/src/tools/resolve.ts +1 -0
- package/src/tts/tts-client.ts +2 -7
- package/src/tui/hyperlink.ts +6 -3
- package/src/utils/image-loading.ts +12 -2
- package/src/utils/ipc.ts +38 -0
- package/src/web/search/providers/perplexity-auth.ts +133 -0
- package/src/web/search/providers/perplexity.ts +2 -125
|
@@ -201,9 +201,9 @@ export declare function resolveModelScope(patterns: string[], modelRegistry: Pic
|
|
|
201
201
|
* the result to models matching those patterns.
|
|
202
202
|
*
|
|
203
203
|
* Returns the unfiltered available list when `enabledModels` is empty.
|
|
204
|
-
* Returns an empty list when `enabledModels` is configured but no
|
|
205
|
-
*
|
|
206
|
-
*
|
|
204
|
+
* Returns an empty list when `enabledModels` is configured but no model matches
|
|
205
|
+
* any pattern — callers MUST treat this as "no usable model" rather than
|
|
206
|
+
* falling back to the global default (see issue #1022).
|
|
207
207
|
*/
|
|
208
208
|
export declare function resolveAllowedModels(modelRegistry: Pick<ModelRegistry, "getAvailable" | "getCanonicalVariants">, settings: Settings | undefined, preferences?: ModelMatchPreferences): Promise<Model<Api>[]>;
|
|
209
209
|
/**
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Subprocess } from "bun";
|
|
2
|
+
import type { MnemopiEmbedModelId, MnemopiEmbedWorkerInbound, MnemopiEmbedWorkerOutbound } from "./embed-protocol";
|
|
3
|
+
/**
|
|
4
|
+
* Abstraction over the mnemopi embeddings subprocess. The runtime
|
|
5
|
+
* implementation is a Bun child process so `onnxruntime-node`'s NAPI
|
|
6
|
+
* constructor + finalizer never run inside the main agent address space —
|
|
7
|
+
* those destructors segfault Bun on Windows when mnemopi's local embedding
|
|
8
|
+
* provider loads fastembed in the main process (issue #3031; the mnemopi
|
|
9
|
+
* sibling of the tiny-model fix from #1606 / #1607).
|
|
10
|
+
*/
|
|
11
|
+
export interface MnemopiEmbedWorkerHandle {
|
|
12
|
+
send(message: MnemopiEmbedWorkerInbound): void;
|
|
13
|
+
onMessage(handler: (message: MnemopiEmbedWorkerOutbound) => void): () => void;
|
|
14
|
+
onError(handler: (error: Error) => void): () => void;
|
|
15
|
+
terminate(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Hidden subcommand on the main CLI that boots the mnemopi embeddings worker
|
|
19
|
+
* in the spawned subprocess. Kept in sync with the dispatch in `cli.ts`.
|
|
20
|
+
*/
|
|
21
|
+
export declare const MNEMOPI_EMBED_WORKER_ARG = "__omp_worker_mnemopi_embed";
|
|
22
|
+
interface SpawnedSubprocess {
|
|
23
|
+
proc: Subprocess<"ignore", "ignore", "ignore">;
|
|
24
|
+
inbound: Set<(message: MnemopiEmbedWorkerOutbound) => void>;
|
|
25
|
+
errors: Set<(error: Error) => void>;
|
|
26
|
+
/**
|
|
27
|
+
* Flipped to `true` right before the deliberate SIGKILL so `onExit` can
|
|
28
|
+
* distinguish the expected hard-kill from a crash (SIGSEGV from a native
|
|
29
|
+
* fault, OOM SIGKILL, operator `kill -9`). Only the latter surfaces as a
|
|
30
|
+
* worker error so callers don't await forever.
|
|
31
|
+
*/
|
|
32
|
+
intentionalExit: {
|
|
33
|
+
value: boolean;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Spawn the mnemopi embeddings worker as a subprocess. Exported for tests and
|
|
38
|
+
* the smoke probe; production callers go through {@link spawnMnemopiEmbedWorker}.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createMnemopiEmbedSubprocess(): SpawnedSubprocess;
|
|
41
|
+
/**
|
|
42
|
+
* Per-model wrapper produced by {@link MnemopiEmbedClient.initialize}.
|
|
43
|
+
* `embed()` round-trips one batch of texts through the worker subprocess and
|
|
44
|
+
* yields the resulting vectors in a single asynchronous batch — fastembed's
|
|
45
|
+
* own iterator was emitting batches that we collect on the child side anyway,
|
|
46
|
+
* and serializing per-batch over IPC would not improve throughput.
|
|
47
|
+
*/
|
|
48
|
+
export interface MnemopiSubprocessEmbeddingModel {
|
|
49
|
+
embed(texts: string[], batchSize?: number): AsyncIterable<number[][]>;
|
|
50
|
+
}
|
|
51
|
+
export declare class MnemopiEmbedClient {
|
|
52
|
+
#private;
|
|
53
|
+
constructor(spawnWorker?: () => MnemopiEmbedWorkerHandle);
|
|
54
|
+
/**
|
|
55
|
+
* Load the named fastembed model inside the subprocess. Resolves to a
|
|
56
|
+
* thin wrapper whose `embed()` round-trips through the same worker, or
|
|
57
|
+
* `null` when the worker cannot init the model (missing peer, native
|
|
58
|
+
* load failure, etc.). Multiple calls with the same model reuse the
|
|
59
|
+
* single in-flight worker; calling with a different model loads it on
|
|
60
|
+
* the child without restarting the process.
|
|
61
|
+
*/
|
|
62
|
+
initialize(model: MnemopiEmbedModelId, cacheDir: string | undefined): Promise<MnemopiSubprocessEmbeddingModel | null>;
|
|
63
|
+
terminate(): Promise<void>;
|
|
64
|
+
}
|
|
65
|
+
export declare const mnemopiEmbedClient: MnemopiEmbedClient;
|
|
66
|
+
export declare function shutdownMnemopiEmbedClient(): Promise<void>;
|
|
67
|
+
export declare function smokeTestMnemopiEmbedWorker({ timeoutMs, }?: {
|
|
68
|
+
timeoutMs?: number;
|
|
69
|
+
}): Promise<void>;
|
|
70
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire types between the parent (`MnemopiEmbedClient`) and the local
|
|
3
|
+
* embeddings subprocess. The parent owns the subprocess lifecycle (graceful
|
|
4
|
+
* work, hard `SIGKILL` on shutdown); the protocol carries no explicit close
|
|
5
|
+
* handshake — once the parent decides to terminate, it signals the OS to reap
|
|
6
|
+
* the child so `onnxruntime-node`'s NAPI finalizer never runs in the main
|
|
7
|
+
* agent address space (it crashes Bun on Windows shutdown — issue #3031, the
|
|
8
|
+
* mnemopi sibling of the tiny-model fix from #1606/#1607). See
|
|
9
|
+
* `embed-client.ts` for the spawn/kill glue.
|
|
10
|
+
*/
|
|
11
|
+
/** Identifier of the fastembed model the worker should load (e.g. `fast-bge-base-en-v1.5`). */
|
|
12
|
+
export type MnemopiEmbedModelId = string;
|
|
13
|
+
export type MnemopiEmbedWorkerInbound = {
|
|
14
|
+
type: "ping";
|
|
15
|
+
id: string;
|
|
16
|
+
} | {
|
|
17
|
+
type: "init";
|
|
18
|
+
id: string;
|
|
19
|
+
model: MnemopiEmbedModelId;
|
|
20
|
+
cacheDir?: string;
|
|
21
|
+
} | {
|
|
22
|
+
type: "embed";
|
|
23
|
+
id: string;
|
|
24
|
+
model: MnemopiEmbedModelId;
|
|
25
|
+
cacheDir?: string;
|
|
26
|
+
texts: string[];
|
|
27
|
+
batchSize?: number;
|
|
28
|
+
};
|
|
29
|
+
export type MnemopiEmbedWorkerOutbound = {
|
|
30
|
+
type: "pong";
|
|
31
|
+
id: string;
|
|
32
|
+
} | {
|
|
33
|
+
type: "ready";
|
|
34
|
+
id: string;
|
|
35
|
+
} | {
|
|
36
|
+
type: "vectors";
|
|
37
|
+
id: string;
|
|
38
|
+
vectors: number[][];
|
|
39
|
+
} | {
|
|
40
|
+
type: "error";
|
|
41
|
+
id: string;
|
|
42
|
+
error: string;
|
|
43
|
+
} | {
|
|
44
|
+
type: "log";
|
|
45
|
+
level: "debug" | "warn" | "error";
|
|
46
|
+
msg: string;
|
|
47
|
+
meta?: Record<string, unknown>;
|
|
48
|
+
};
|
|
49
|
+
export interface MnemopiEmbedTransport {
|
|
50
|
+
send(message: MnemopiEmbedWorkerOutbound): void;
|
|
51
|
+
onMessage(handler: (message: MnemopiEmbedWorkerInbound) => void): () => void;
|
|
52
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mnemopi local-embeddings worker. Loaded inside the dedicated subprocess
|
|
3
|
+
* spawned by `embed-client.ts` (re-entered through the agent CLI's hidden
|
|
4
|
+
* `__omp_worker_mnemopi_embed` selector). The whole point of this module is
|
|
5
|
+
* that `loadFastembed()` — and therefore `onnxruntime-node`'s NAPI
|
|
6
|
+
* constructor + finalizer — only ever runs in this child address space. The
|
|
7
|
+
* parent `SIGKILL`s us on shutdown so the destructor that crashes Bun on
|
|
8
|
+
* Windows shutdown (issue #3031, mnemopi sibling of #1606/#1607) never runs
|
|
9
|
+
* in either process.
|
|
10
|
+
*/
|
|
11
|
+
import type { MnemopiEmbedTransport } from "./embed-protocol";
|
|
12
|
+
export declare function startMnemopiEmbedWorker(transport: MnemopiEmbedTransport): void;
|
|
@@ -4,7 +4,15 @@ import type { Mnemopi, RecallResult } from "@oh-my-pi/pi-mnemopi";
|
|
|
4
4
|
import type * as MnemopiCoreNs from "@oh-my-pi/pi-mnemopi/core";
|
|
5
5
|
import type { AgentSession } from "../session/agent-session";
|
|
6
6
|
import type { MnemopiBackendConfig } from "./config";
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* Lazily load `@oh-my-pi/pi-mnemopi` (memoized) and route fastembed loads
|
|
9
|
+
* through the dedicated embeddings subprocess. The override is installed once
|
|
10
|
+
* — before any consumer gets the chance to call `embed()` — so
|
|
11
|
+
* `onnxruntime-node`'s NAPI constructor + finalizer never run inside the
|
|
12
|
+
* agent's address space (issue #3031). Test seams that swap the initializer
|
|
13
|
+
* with `setLocalModelInitializerForTests` still win because both go through
|
|
14
|
+
* the same module-level slot.
|
|
15
|
+
*/
|
|
8
16
|
export declare function loadMnemopi(): Promise<typeof MnemopiNs>;
|
|
9
17
|
/** Lazily load `@oh-my-pi/pi-mnemopi/core` (memoized). */
|
|
10
18
|
export declare function loadMnemopiCore(): Promise<typeof MnemopiCoreNs>;
|
|
@@ -6,20 +6,33 @@ export interface CacheInvalidation {
|
|
|
6
6
|
reprocessedTokens: number;
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
|
-
* Decide whether `current` turn lost
|
|
9
|
+
* Decide whether `current` turn lost a *working* prompt cache that `prev` was
|
|
10
|
+
* reusing.
|
|
10
11
|
*
|
|
11
12
|
* The provider reports a warm prefix as `cacheRead`; a model/thinking/tool/
|
|
12
13
|
* system-prompt change (or a history rewrite) breaks the prefix, so the next
|
|
13
|
-
* request reads nothing from cache and re-pays for the whole prompt. We
|
|
14
|
-
*
|
|
14
|
+
* request reads nothing from cache and re-pays for the whole prompt. We flag
|
|
15
|
+
* only the transition where a demonstrably warm cache goes cold: the previous
|
|
16
|
+
* turn must have actually READ a meaningful prefix back, and this turn's
|
|
15
17
|
* `cacheRead` collapsed to zero while it still reprocessed a non-trivial prompt.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
18
|
+
*
|
|
19
|
+
* Requiring a prior warm read is deliberate. A turn that merely WROTE the prefix
|
|
20
|
+
* (`cacheRead` 0) has not proven the cache is live — that is the session's first
|
|
21
|
+
* request, or a re-write after expiry — so a following cold turn there is
|
|
22
|
+
* expected, not an invalidation the user caused (e.g. a long-running first tool
|
|
23
|
+
* call outliving the provider's 5-minute cache TTL surfaced a spurious "cache
|
|
24
|
+
* miss" right under the opening message). It also collapses a run of consecutive
|
|
25
|
+
* cold turns to the single marker at the moment the cache actually broke, instead
|
|
26
|
+
* of repeating the banner on every turn while it re-warms.
|
|
27
|
+
*
|
|
28
|
+
* Returns `undefined` (no marker) for the first turn, turns whose predecessor
|
|
29
|
+
* never read a warm prefix, tiny contexts, turns that reused any cache, and —
|
|
30
|
+
* crucially — turns on providers with *implicit* best-effort caching. Only an
|
|
31
|
+
* explicit, prefix-controlled cache (Anthropic / Bedrock `cache_control`)
|
|
32
|
+
* re-creates the prefix on a cold turn (`cacheWrite > 0`); implicit caches
|
|
33
|
+
* (Google / OpenAI / Fireworks) report `cacheWrite: 0` and drop `cacheRead` to
|
|
34
|
+
* zero intermittently as routine propagation noise that self-heals the next
|
|
35
|
+
* turn, so flagging it would be a false positive.
|
|
23
36
|
*/
|
|
24
37
|
export declare function detectCacheInvalidation(prev: Usage | undefined, current: Usage): CacheInvalidation | undefined;
|
|
25
38
|
/**
|
|
@@ -34,9 +34,8 @@ export declare class StatusLineComponent implements Component {
|
|
|
34
34
|
dispose(): void;
|
|
35
35
|
invalidate(): void;
|
|
36
36
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* (non-private) so unit tests can verify the backoff invariant.
|
|
37
|
+
* Startup redraws only arm a short-delayed task; timeout releases the render
|
|
38
|
+
* cadence while a late successful fetch can still refresh the cached segment.
|
|
40
39
|
*/
|
|
41
40
|
refreshUsageInBackground(): void;
|
|
42
41
|
/**
|
package/dist/types/sdk.d.ts
CHANGED
|
@@ -262,6 +262,18 @@ export declare function discoverSessionExtensionPaths(options: Pick<CreateAgentS
|
|
|
262
262
|
* repeated. Keep this the single source of the discovery branch logic.
|
|
263
263
|
*/
|
|
264
264
|
export declare function loadSessionExtensions(options: Pick<CreateAgentSessionOptions, "disableExtensionDiscovery" | "additionalExtensionPaths">, cwd: string, settings: Settings, eventBus: EventBus): Promise<LoadExtensionsResult>;
|
|
265
|
+
/**
|
|
266
|
+
* Load discovered/configured extensions and register their providers into
|
|
267
|
+
* `modelRegistry`, then discover the dynamic provider catalogs. One-shot CLIs
|
|
268
|
+
* (`omp bench`, dry-balance) build a bare {@link ModelRegistry} that only knows
|
|
269
|
+
* built-in catalog providers; without this, providers contributed by an
|
|
270
|
+
* extension (e.g. a custom OpenAI-compatible provider under
|
|
271
|
+
* `~/.omp/agent/extensions/`) never reach model resolution. Mirrors the
|
|
272
|
+
* session / `omp models` path: drain the queued provider registrations, then
|
|
273
|
+
* `refreshRuntimeProviders` so dynamically-discovered models exist before
|
|
274
|
+
* selectors are resolved.
|
|
275
|
+
*/
|
|
276
|
+
export declare function loadCliExtensionProviders(modelRegistry: ModelRegistry, settings: Settings, cwd: string, options?: Pick<CreateAgentSessionOptions, "disableExtensionDiscovery" | "additionalExtensionPaths">): Promise<void>;
|
|
265
277
|
/**
|
|
266
278
|
* Discover skills from cwd and agentDir.
|
|
267
279
|
*/
|
|
@@ -399,6 +399,8 @@ export declare class AgentSession {
|
|
|
399
399
|
nextToolChoiceDirective(): ToolChoiceDirective | undefined;
|
|
400
400
|
/** Peek the head non-forcing pending preview invoker, for the `resolve` tool's dispatch. */
|
|
401
401
|
peekPendingInvoker(): ((input: unknown) => Promise<unknown> | unknown) | undefined;
|
|
402
|
+
/** Clear stale non-forcing pending preview invokers after `resolve` proves none can run. */
|
|
403
|
+
clearPendingInvokers(): void;
|
|
402
404
|
/**
|
|
403
405
|
* Force the next model call to target a specific active tool, then terminate
|
|
404
406
|
* the agent loop. Pushes a two-step sequence [forced, "none"] so the model
|
|
@@ -16,6 +16,8 @@ export declare class AgentStorage {
|
|
|
16
16
|
* @returns AgentStorage instance for the given path
|
|
17
17
|
*/
|
|
18
18
|
static open(dbPath?: string): Promise<AgentStorage>;
|
|
19
|
+
/** @internal Reset all singletons and close their databases — test-only. */
|
|
20
|
+
static resetInstance(): void;
|
|
19
21
|
/**
|
|
20
22
|
* Reads legacy settings persisted in the agent.db `settings` table.
|
|
21
23
|
* The canonical settings store is `config.yml`; this accessor only
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
* `runRootCommand`, and we want hand-edited config entries to be honoured at
|
|
21
21
|
* boot without forcing a startup reorder.
|
|
22
22
|
*/
|
|
23
|
-
import { type AuthBrokerClientConfig, type DiscoverAuthStorageOptions,
|
|
23
|
+
import { type AuthBrokerClientConfig, type DiscoverAuthStorageOptions, getAuthBrokerTokenFilePath } from "@oh-my-pi/pi-ai/auth-broker/discover";
|
|
24
|
+
import type { AuthStorage } from "./auth-storage";
|
|
24
25
|
export { type AuthBrokerClientConfig, getAuthBrokerTokenFilePath };
|
|
25
26
|
/**
|
|
26
27
|
* Read broker configuration. Returns null when the URL is missing
|
|
@@ -41,4 +42,4 @@ export declare function resolveAuthBrokerConfig(): Promise<AuthBrokerClientConfi
|
|
|
41
42
|
*
|
|
42
43
|
* Default `agentDir` is the current configured agent directory.
|
|
43
44
|
*/
|
|
44
|
-
export declare function discoverAuthStorage(agentDir?: string, options?: Omit<DiscoverAuthStorageOptions, "agentDir" | "configValueResolver">):
|
|
45
|
+
export declare function discoverAuthStorage(agentDir?: string, options?: Omit<DiscoverAuthStorageOptions, "agentDir" | "configValueResolver">): Promise<AuthStorage>;
|
|
@@ -10,7 +10,7 @@ export declare class HistoryStorage {
|
|
|
10
10
|
#private;
|
|
11
11
|
private constructor();
|
|
12
12
|
static open(dbPath?: string): HistoryStorage;
|
|
13
|
-
/** @internal Reset the singleton — test-only. */
|
|
13
|
+
/** @internal Reset the singleton and close its database — test-only. */
|
|
14
14
|
static resetInstance(): void;
|
|
15
15
|
/**
|
|
16
16
|
* Register a resolver that supplies the current session ID for prompts added
|
|
@@ -71,6 +71,8 @@ export declare class ToolChoiceQueue {
|
|
|
71
71
|
registerPendingInvoker(id: string, sourceToolName: string, onInvoked: (input: unknown) => Promise<unknown> | unknown): void;
|
|
72
72
|
/** Drop the pending invoker with this id (e.g. after it resolves). */
|
|
73
73
|
removePendingInvoker(id: string): void;
|
|
74
|
+
/** Drop every pending preview invoker without touching hard tool-choice directives. */
|
|
75
|
+
clearPendingInvokers(): void;
|
|
74
76
|
/** True when at least one non-forcing pending preview is registered. */
|
|
75
77
|
get hasPendingInvoker(): boolean;
|
|
76
78
|
/** The head (most-recently registered) pending invoker's handler, for resolve dispatch. */
|
|
@@ -61,6 +61,6 @@ export declare function isImageProviderPreference(value: unknown): value is Imag
|
|
|
61
61
|
/** Set the preferred image provider from settings */
|
|
62
62
|
export declare function setPreferredImageProvider(provider: ImageProviderPreference): void;
|
|
63
63
|
export declare const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails>;
|
|
64
|
-
export declare function getImageGenTools(
|
|
65
|
-
export declare function getImageGenToolsWithRegistry(
|
|
64
|
+
export declare function getImageGenTools(_modelRegistry?: ModelRegistry, _activeModel?: Model): Promise<Array<CustomTool<typeof imageGenSchema, ImageGenToolDetails>>>;
|
|
65
|
+
export declare function getImageGenToolsWithRegistry(_modelRegistry: ModelRegistry, _activeModel?: Model): Promise<Array<CustomTool<typeof imageGenSchema, ImageGenToolDetails>>>;
|
|
66
66
|
export {};
|
|
@@ -276,6 +276,8 @@ export interface ToolSession {
|
|
|
276
276
|
* tool dispatches to it so a staged preview resolves WITHOUT forcing tool_choice — the
|
|
277
277
|
* agent-loop's SoftToolRequirement lifecycle owns reminder injection and escalation. */
|
|
278
278
|
peekPendingInvoker?(): ((input: unknown) => Promise<unknown> | unknown) | undefined;
|
|
279
|
+
/** Clear stale pending preview markers when `resolve` cannot dispatch them. */
|
|
280
|
+
clearPendingInvokers?(): void;
|
|
279
281
|
/** Peek the long-lived "standing" resolve handler registered by a mode (e.g. plan mode).
|
|
280
282
|
* Consulted by the `resolve` tool as a fallback when no queue invoker is in flight,
|
|
281
283
|
* letting modes accept `resolve` invocations without forcing the tool choice every turn. */
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* - `"off"`: never
|
|
6
6
|
* - `"auto"`: when `process.stdout.isTTY`, `NO_COLOR` is unset, and the detected terminal reports hyperlink support
|
|
7
7
|
* - `"always"`: unconditionally (useful for viewers that support OSC 8 without advertising it)
|
|
8
|
+
* Before settings initialization, returns false so early render paths stay plain text.
|
|
8
9
|
*/
|
|
9
10
|
export declare function isHyperlinkEnabled(): boolean;
|
|
10
11
|
/**
|
|
@@ -23,8 +24,8 @@ export declare function urlHyperlink(url: string, displayText: string): string;
|
|
|
23
24
|
* Wrap `displayText` in an OSC 8 hyperlink pointing at an HTTP(S) URL,
|
|
24
25
|
* bypassing terminal capability auto-detection. Used for auth prompts where
|
|
25
26
|
* an inert "click" label blocks login on terminals whose capabilities are
|
|
26
|
-
* not advertised. Still returns plain text
|
|
27
|
-
* opted out via `tui.hyperlinks=off`.
|
|
27
|
+
* not advertised. Still returns plain text before settings initialization or
|
|
28
|
+
* when the user has explicitly opted out via `tui.hyperlinks=off`.
|
|
28
29
|
*/
|
|
29
30
|
export declare function urlHyperlinkAlways(url: string, displayText: string): string;
|
|
30
31
|
/**
|
|
@@ -8,7 +8,7 @@ export declare const SUPPORTED_INPUT_IMAGE_MIME_TYPES: Set<string>;
|
|
|
8
8
|
* with an opaque HTTP 400. Detect those models so the resize pipeline encodes
|
|
9
9
|
* to PNG/JPEG instead — the automatic equivalent of `OMP_NO_WEBP=1`.
|
|
10
10
|
*/
|
|
11
|
-
export declare function modelLacksWebpSupport(model: Pick<Model, "provider" | "api"> | undefined): boolean;
|
|
11
|
+
export declare function modelLacksWebpSupport(model: Pick<Model, "provider" | "api" | "imageInputDecoder"> | undefined): boolean;
|
|
12
12
|
/**
|
|
13
13
|
* `true` when `model` cannot decode WebP, otherwise `undefined` so the
|
|
14
14
|
* `OMP_NO_WEBP` env fallback in {@link resizeImage} still applies. Feed straight
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Narrow a value to a thenable so a rejection handler can be attached.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the local helper in `mcp/transports/stdio.ts` (kept separate because
|
|
5
|
+
* that copy serves the FileSink stdin-write path and is battle-tested there).
|
|
6
|
+
* This shared copy is the home for the IPC `send()` sites.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isThenable(value: unknown): value is PromiseLike<unknown>;
|
|
9
|
+
/**
|
|
10
|
+
* Send a message to a Bun subprocess over IPC, neutralizing both the
|
|
11
|
+
* synchronous throw ("cannot be used after the process has exited") and any
|
|
12
|
+
* asynchronous rejection (EPIPE from a pipe that broke between exit being
|
|
13
|
+
* observed and the next `send()`). The dead worker is detected separately via
|
|
14
|
+
* `onExit`/`onError` and respawned or disabled by the owning client; an
|
|
15
|
+
* un-awaited EPIPE rejection must not escape as a fatal unhandled rejection
|
|
16
|
+
* that takes down the whole session. See issue #2997.
|
|
17
|
+
*
|
|
18
|
+
* `label` prefixes the debug log on synchronous failure (e.g. "tts").
|
|
19
|
+
*/
|
|
20
|
+
export declare function safeSend(proc: {
|
|
21
|
+
send(message: unknown): unknown;
|
|
22
|
+
}, message: unknown, label: string): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { AuthStorage, OAuthAccess } from "@oh-my-pi/pi-ai";
|
|
2
|
+
export declare const PERPLEXITY_CHAT_BASE_URL = "https://api.perplexity.ai";
|
|
3
|
+
export declare const PERPLEXITY_RESPONSES_BASE_URL = "https://api.perplexity.ai/v1";
|
|
4
|
+
export declare const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
|
|
5
|
+
export declare const OAUTH_EXPIRY_BUFFER_MS: number;
|
|
6
|
+
export interface ApiConfig {
|
|
7
|
+
type: "api_key";
|
|
8
|
+
apiKey: string;
|
|
9
|
+
provider: "perplexity" | "openrouter";
|
|
10
|
+
chatBaseUrl: string;
|
|
11
|
+
responsesBaseUrl: string;
|
|
12
|
+
modelPrefix: string;
|
|
13
|
+
useResponses: boolean;
|
|
14
|
+
}
|
|
15
|
+
export type PerplexityAuth = ApiConfig | {
|
|
16
|
+
type: "oauth";
|
|
17
|
+
access: OAuthAccess;
|
|
18
|
+
} | {
|
|
19
|
+
type: "cookies";
|
|
20
|
+
cookies: string;
|
|
21
|
+
} | {
|
|
22
|
+
type: "anonymous";
|
|
23
|
+
};
|
|
24
|
+
export interface PerplexityAuthOptions {
|
|
25
|
+
signal?: AbortSignal;
|
|
26
|
+
forceRefresh?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** Detect API-key endpoints to try in priority order (Perplexity direct, then OpenRouter). */
|
|
29
|
+
export declare function getApiConfigs(authStorage: AuthStorage, sessionId: string | undefined, options?: PerplexityAuthOptions): Promise<ApiConfig[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Decode a Perplexity JWT's `exp` claim, in ms. Returns `undefined` when the
|
|
32
|
+
* token has no `exp` (which is the common case — Perplexity sessions are
|
|
33
|
+
* server-side and effectively non-expiring from the client's POV).
|
|
34
|
+
*/
|
|
35
|
+
export declare function jwtExpiryMs(token: string): number | undefined;
|
|
36
|
+
/** Collect all available auth methods to try in priority order */
|
|
37
|
+
export declare function getAvailableAuthMethods(authStorage: AuthStorage, sessionId: string | undefined, options?: PerplexityAuthOptions): Promise<PerplexityAuth[]>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "16.1.
|
|
4
|
+
"version": "16.1.4",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -48,17 +48,17 @@
|
|
|
48
48
|
"@agentclientprotocol/sdk": "0.25.0",
|
|
49
49
|
"@babel/parser": "^7.29.7",
|
|
50
50
|
"@mozilla/readability": "^0.6.0",
|
|
51
|
-
"@oh-my-pi/hashline": "16.1.
|
|
52
|
-
"@oh-my-pi/omp-stats": "16.1.
|
|
53
|
-
"@oh-my-pi/pi-agent-core": "16.1.
|
|
54
|
-
"@oh-my-pi/pi-ai": "16.1.
|
|
55
|
-
"@oh-my-pi/pi-catalog": "16.1.
|
|
56
|
-
"@oh-my-pi/pi-mnemopi": "16.1.
|
|
57
|
-
"@oh-my-pi/pi-natives": "16.1.
|
|
58
|
-
"@oh-my-pi/pi-tui": "16.1.
|
|
59
|
-
"@oh-my-pi/pi-utils": "16.1.
|
|
60
|
-
"@oh-my-pi/pi-wire": "16.1.
|
|
61
|
-
"@oh-my-pi/snapcompact": "16.1.
|
|
51
|
+
"@oh-my-pi/hashline": "16.1.4",
|
|
52
|
+
"@oh-my-pi/omp-stats": "16.1.4",
|
|
53
|
+
"@oh-my-pi/pi-agent-core": "16.1.4",
|
|
54
|
+
"@oh-my-pi/pi-ai": "16.1.4",
|
|
55
|
+
"@oh-my-pi/pi-catalog": "16.1.4",
|
|
56
|
+
"@oh-my-pi/pi-mnemopi": "16.1.4",
|
|
57
|
+
"@oh-my-pi/pi-natives": "16.1.4",
|
|
58
|
+
"@oh-my-pi/pi-tui": "16.1.4",
|
|
59
|
+
"@oh-my-pi/pi-utils": "16.1.4",
|
|
60
|
+
"@oh-my-pi/pi-wire": "16.1.4",
|
|
61
|
+
"@oh-my-pi/snapcompact": "16.1.4",
|
|
62
62
|
"@opentelemetry/api": "^1.9.1",
|
|
63
63
|
"@opentelemetry/context-async-hooks": "^2.7.1",
|
|
64
64
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.218.0",
|
package/src/cli/bench-cli.ts
CHANGED
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
} from "../config/model-resolver";
|
|
26
26
|
import { Settings } from "../config/settings";
|
|
27
27
|
import benchPrompt from "../prompts/bench.md" with { type: "text" };
|
|
28
|
-
import { discoverAuthStorage } from "../sdk";
|
|
28
|
+
import { discoverAuthStorage, loadCliExtensionProviders } from "../sdk";
|
|
29
29
|
import { resolveThinkingLevelForModel, shouldDisableReasoning, toReasoningEffort } from "../thinking";
|
|
30
30
|
|
|
31
31
|
const DEFAULT_RUNS = 1;
|
|
@@ -145,6 +145,23 @@ function isFirstTokenEvent(event: AssistantMessageEvent): boolean {
|
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
/** Final message carries visible output — non-empty text/thinking or a tool call. */
|
|
149
|
+
function hasVisibleFinalContent(message: AssistantMessage): boolean {
|
|
150
|
+
return message.content.some(block => {
|
|
151
|
+
switch (block.type) {
|
|
152
|
+
case "text":
|
|
153
|
+
return block.text.length > 0;
|
|
154
|
+
case "thinking":
|
|
155
|
+
return block.thinking.length > 0;
|
|
156
|
+
case "redactedThinking":
|
|
157
|
+
case "toolCall":
|
|
158
|
+
return true;
|
|
159
|
+
default:
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
148
165
|
/**
|
|
149
166
|
* Tokens/s over the generation window (duration minus TTFT) so queue/prefill
|
|
150
167
|
* latency does not dilute throughput. Falls back to total duration when the
|
|
@@ -232,6 +249,18 @@ async function runBenchRequest(
|
|
|
232
249
|
const rawTtft = message.ttft ?? (firstTokenAt === undefined ? durationMs : firstTokenAt - startedAt);
|
|
233
250
|
const ttftMs = Number.isFinite(rawTtft) && rawTtft > 0 ? rawTtft : 0;
|
|
234
251
|
const outputTokens = Number.isFinite(message.usage.output) && message.usage.output > 0 ? message.usage.output : 0;
|
|
252
|
+
// A run that streamed no content (no delta/end event set firstTokenAt),
|
|
253
|
+
// carries no visible final content, and measured no output tokens
|
|
254
|
+
// benchmarked nothing — a genuinely empty stream (e.g. a gateway that 200s
|
|
255
|
+
// with an empty body). Surface it as a failure instead of a misleading
|
|
256
|
+
// 0-token "✓". Streaming and buffered providers that produce content keep
|
|
257
|
+
// passing even when usage is omitted.
|
|
258
|
+
if (firstTokenAt === undefined && outputTokens === 0 && !hasVisibleFinalContent(message)) {
|
|
259
|
+
return {
|
|
260
|
+
ok: false,
|
|
261
|
+
error: `provider returned no output (0 tokens, empty stream; stop reason: ${message.stopReason ?? "unknown"})`,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
235
264
|
return {
|
|
236
265
|
ok: true,
|
|
237
266
|
ttftMs,
|
|
@@ -328,8 +357,10 @@ export function formatBenchTable(summary: BenchSummary): string {
|
|
|
328
357
|
async function createDefaultRuntime(): Promise<BenchRuntime> {
|
|
329
358
|
const authStorage = await discoverAuthStorage();
|
|
330
359
|
try {
|
|
331
|
-
const
|
|
360
|
+
const cwd = getProjectDir();
|
|
361
|
+
const settings = await Settings.init({ cwd });
|
|
332
362
|
const modelRegistry = new ModelRegistry(authStorage);
|
|
363
|
+
await loadCliExtensionProviders(modelRegistry, settings, cwd);
|
|
333
364
|
return {
|
|
334
365
|
modelRegistry,
|
|
335
366
|
settings,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
} from "../config/model-resolver";
|
|
27
27
|
import { Settings } from "../config/settings";
|
|
28
28
|
import dryBalanceBenchPrompt from "../prompts/dry-balance-bench.md" with { type: "text" };
|
|
29
|
-
import { discoverAuthStorage } from "../sdk";
|
|
29
|
+
import { discoverAuthStorage, loadCliExtensionProviders } from "../sdk";
|
|
30
30
|
|
|
31
31
|
const DEFAULT_SAMPLE_COUNT = 100;
|
|
32
32
|
const DEFAULT_CONCURRENCY = 32;
|
|
@@ -523,8 +523,10 @@ async function runBenchTargets(
|
|
|
523
523
|
async function createDefaultRuntime(): Promise<DryBalanceRuntime> {
|
|
524
524
|
const authStorage = await discoverAuthStorage();
|
|
525
525
|
try {
|
|
526
|
-
const
|
|
526
|
+
const cwd = getProjectDir();
|
|
527
|
+
const settings = await Settings.init({ cwd });
|
|
527
528
|
const modelRegistry = new ModelRegistry(authStorage);
|
|
529
|
+
await loadCliExtensionProviders(modelRegistry, settings, cwd);
|
|
528
530
|
return {
|
|
529
531
|
modelRegistry,
|
|
530
532
|
settings,
|
package/src/cli.ts
CHANGED
|
@@ -68,6 +68,7 @@ async function runSmokeTest(): Promise<void> {
|
|
|
68
68
|
const { smokeTestTinyTitleWorker } = await import("./tiny/title-client");
|
|
69
69
|
const { smokeTestSttWorker } = await import("./stt/asr-client");
|
|
70
70
|
const { smokeTestTtsWorker } = await import("./tts/tts-client");
|
|
71
|
+
const { smokeTestMnemopiEmbedWorker } = await import("./mnemopi/embed-client");
|
|
71
72
|
const { smokeTestJsEvalWorker } = await import("./eval/js/context-manager");
|
|
72
73
|
await smokeTestSyncWorker();
|
|
73
74
|
|
|
@@ -87,6 +88,7 @@ async function runSmokeTest(): Promise<void> {
|
|
|
87
88
|
await smokeTestSttWorker();
|
|
88
89
|
await smokeTestJsEvalWorker();
|
|
89
90
|
await smokeTestTtsWorker();
|
|
91
|
+
await smokeTestMnemopiEmbedWorker();
|
|
90
92
|
process.stdout.write("smoke-test: ok\n");
|
|
91
93
|
}
|
|
92
94
|
|
|
@@ -96,6 +98,7 @@ const TAB_WORKER_ARG = "__omp_worker_tab";
|
|
|
96
98
|
const JS_EVAL_WORKER_ARG = "__omp_worker_js_eval";
|
|
97
99
|
const STT_WORKER_ARG = "__omp_worker_stt";
|
|
98
100
|
const TTS_WORKER_ARG = "__omp_worker_tts";
|
|
101
|
+
const MNEMOPI_EMBED_WORKER_ARG = "__omp_worker_mnemopi_embed";
|
|
99
102
|
|
|
100
103
|
async function runWorkerEntrypoint(arg: string | undefined): Promise<boolean> {
|
|
101
104
|
if (arg === TINY_WORKER_ARG) {
|
|
@@ -151,6 +154,11 @@ async function runWorkerEntrypoint(arg: string | undefined): Promise<boolean> {
|
|
|
151
154
|
await runIpcSubprocessWorker(startTtsWorker);
|
|
152
155
|
return true;
|
|
153
156
|
}
|
|
157
|
+
if (arg === MNEMOPI_EMBED_WORKER_ARG) {
|
|
158
|
+
const { startMnemopiEmbedWorker } = await import("./mnemopi/embed-worker");
|
|
159
|
+
await runIpcSubprocessWorker(startMnemopiEmbedWorker);
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
154
162
|
return false;
|
|
155
163
|
}
|
|
156
164
|
|