@code-yeongyu/senpi 2026.8.11-2 → 2026.8.11-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 +90 -0
- package/dist/cli-main.d.ts +1 -1
- package/dist/cli-main.d.ts.map +1 -1
- package/dist/cli-main.js +1 -0
- package/dist/cli-main.js.map +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-session.d.ts +8 -2
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +63 -7
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/availability.d.ts +2 -0
- package/dist/core/extensions/builtin/claude-sdk-oauth/availability.d.ts.map +1 -0
- package/dist/core/extensions/builtin/claude-sdk-oauth/availability.js +24 -0
- package/dist/core/extensions/builtin/claude-sdk-oauth/availability.js.map +1 -0
- package/dist/core/extensions/builtin/claude-sdk-oauth/index.d.ts +4 -0
- package/dist/core/extensions/builtin/claude-sdk-oauth/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/index.js +5 -2
- package/dist/core/extensions/builtin/claude-sdk-oauth/index.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/oauth-login.d.ts +6 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/oauth-login.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/oauth-login.js +16 -0
- package/dist/core/extensions/builtin/claude-sdk-oauth/oauth-login.js.map +1 -1
- package/dist/core/extensions/builtin/goal/transitions.js +2 -1
- package/dist/core/extensions/builtin/goal/transitions.js.map +1 -1
- package/dist/core/extensions/builtin/imagegen/auth.d.ts +45 -0
- package/dist/core/extensions/builtin/imagegen/auth.d.ts.map +1 -0
- package/dist/core/extensions/builtin/imagegen/auth.js +137 -0
- package/dist/core/extensions/builtin/imagegen/auth.js.map +1 -0
- package/dist/core/extensions/builtin/imagegen/index.d.ts +5 -0
- package/dist/core/extensions/builtin/imagegen/index.d.ts.map +1 -0
- package/dist/core/extensions/builtin/imagegen/index.js +46 -0
- package/dist/core/extensions/builtin/imagegen/index.js.map +1 -0
- package/dist/core/extensions/builtin/imagegen/paths.d.ts +26 -0
- package/dist/core/extensions/builtin/imagegen/paths.d.ts.map +1 -0
- package/dist/core/extensions/builtin/imagegen/paths.js +52 -0
- package/dist/core/extensions/builtin/imagegen/paths.js.map +1 -0
- package/dist/core/extensions/builtin/imagegen/skill/SKILL.md +67 -0
- package/dist/core/extensions/builtin/imagegen/state.d.ts +26 -0
- package/dist/core/extensions/builtin/imagegen/state.d.ts.map +1 -0
- package/dist/core/extensions/builtin/imagegen/state.js +35 -0
- package/dist/core/extensions/builtin/imagegen/state.js.map +1 -0
- package/dist/core/extensions/builtin/imagegen/tool.d.ts +31 -0
- package/dist/core/extensions/builtin/imagegen/tool.d.ts.map +1 -0
- package/dist/core/extensions/builtin/imagegen/tool.js +174 -0
- package/dist/core/extensions/builtin/imagegen/tool.js.map +1 -0
- package/dist/core/extensions/builtin/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/index.js +9 -1
- package/dist/core/extensions/builtin/index.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/control-inventory.d.ts +14 -0
- package/dist/core/extensions/builtin/mcp/control-inventory.d.ts.map +1 -0
- package/dist/core/extensions/builtin/mcp/control-inventory.js +22 -0
- package/dist/core/extensions/builtin/mcp/control-inventory.js.map +1 -0
- package/dist/core/extensions/builtin/mcp/expose/native-search.d.ts +2 -44
- package/dist/core/extensions/builtin/mcp/expose/native-search.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/expose/native-search.js +3 -97
- package/dist/core/extensions/builtin/mcp/expose/native-search.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/expose/proxy.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/expose/proxy.js +15 -4
- package/dist/core/extensions/builtin/mcp/expose/proxy.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/expose/session.d.ts +2 -1
- package/dist/core/extensions/builtin/mcp/expose/session.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/expose/session.js +2 -2
- package/dist/core/extensions/builtin/mcp/expose/session.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/expose/tier-b.d.ts +13 -11
- package/dist/core/extensions/builtin/mcp/expose/tier-b.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/expose/tier-b.js +58 -59
- package/dist/core/extensions/builtin/mcp/expose/tier-b.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/index.js +28 -36
- package/dist/core/extensions/builtin/mcp/index.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/service-register.d.ts +2 -1
- package/dist/core/extensions/builtin/mcp/service-register.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/service-register.js +2 -2
- package/dist/core/extensions/builtin/mcp/service-register.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/service-types.d.ts +4 -0
- package/dist/core/extensions/builtin/mcp/service-types.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/service-types.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/service.d.ts +6 -11
- package/dist/core/extensions/builtin/mcp/service.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/service.js +79 -41
- package/dist/core/extensions/builtin/mcp/service.js.map +1 -1
- package/dist/core/extensions/builtin/mcp/wrap.d.ts +2 -0
- package/dist/core/extensions/builtin/mcp/wrap.d.ts.map +1 -1
- package/dist/core/extensions/builtin/mcp/wrap.js +3 -0
- package/dist/core/extensions/builtin/mcp/wrap.js.map +1 -1
- package/dist/core/extensions/builtin/openai-image-gen/externalize.d.ts +27 -0
- package/dist/core/extensions/builtin/openai-image-gen/externalize.d.ts.map +1 -0
- package/dist/core/extensions/builtin/openai-image-gen/externalize.js +133 -0
- package/dist/core/extensions/builtin/openai-image-gen/externalize.js.map +1 -0
- package/dist/core/extensions/builtin/openai-image-gen/gate.d.ts +21 -0
- package/dist/core/extensions/builtin/openai-image-gen/gate.d.ts.map +1 -0
- package/dist/core/extensions/builtin/openai-image-gen/gate.js +63 -0
- package/dist/core/extensions/builtin/openai-image-gen/gate.js.map +1 -0
- package/dist/core/extensions/builtin/openai-image-gen/index.d.ts +14 -0
- package/dist/core/extensions/builtin/openai-image-gen/index.d.ts.map +1 -0
- package/dist/core/extensions/builtin/openai-image-gen/index.js +77 -0
- package/dist/core/extensions/builtin/openai-image-gen/index.js.map +1 -0
- package/dist/core/extensions/builtin/openai-image-gen/inject.d.ts +15 -0
- package/dist/core/extensions/builtin/openai-image-gen/inject.d.ts.map +1 -0
- package/dist/core/extensions/builtin/openai-image-gen/inject.js +42 -0
- package/dist/core/extensions/builtin/openai-image-gen/inject.js.map +1 -0
- package/dist/core/extensions/builtin/terminal/monitor-notify.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/monitor-notify.js +8 -1
- package/dist/core/extensions/builtin/terminal/monitor-notify.js.map +1 -1
- package/dist/core/extensions/builtin/terminal/notify.d.ts +3 -1
- package/dist/core/extensions/builtin/terminal/notify.d.ts.map +1 -1
- package/dist/core/extensions/builtin/terminal/notify.js +4 -1
- package/dist/core/extensions/builtin/terminal/notify.js.map +1 -1
- package/dist/core/extensions/builtin/tool-search/engine/bm25.d.ts +22 -0
- package/dist/core/extensions/builtin/tool-search/engine/bm25.d.ts.map +1 -0
- package/dist/core/extensions/builtin/{mcp/expose → tool-search/engine}/bm25.js +16 -31
- package/dist/core/extensions/builtin/tool-search/engine/bm25.js.map +1 -0
- package/dist/core/extensions/builtin/tool-search/engine/document.d.ts +14 -0
- package/dist/core/extensions/builtin/tool-search/engine/document.d.ts.map +1 -0
- package/dist/core/extensions/builtin/tool-search/engine/document.js +2 -0
- package/dist/core/extensions/builtin/tool-search/engine/document.js.map +1 -0
- package/dist/core/extensions/builtin/tool-search/engine/marker.d.ts +55 -0
- package/dist/core/extensions/builtin/tool-search/engine/marker.d.ts.map +1 -0
- package/dist/core/extensions/builtin/tool-search/engine/marker.js +189 -0
- package/dist/core/extensions/builtin/tool-search/engine/marker.js.map +1 -0
- package/dist/core/extensions/builtin/tool-search/index.d.ts +7 -0
- package/dist/core/extensions/builtin/tool-search/index.d.ts.map +1 -0
- package/dist/core/extensions/builtin/tool-search/index.js +71 -0
- package/dist/core/extensions/builtin/tool-search/index.js.map +1 -0
- package/dist/core/extensions/builtin/tool-search/native-search.d.ts +52 -0
- package/dist/core/extensions/builtin/tool-search/native-search.d.ts.map +1 -0
- package/dist/core/extensions/builtin/tool-search/native-search.js +116 -0
- package/dist/core/extensions/builtin/tool-search/native-search.js.map +1 -0
- package/dist/core/extensions/builtin/tool-search/service.d.ts +38 -0
- package/dist/core/extensions/builtin/tool-search/service.d.ts.map +1 -0
- package/dist/core/extensions/builtin/tool-search/service.js +200 -0
- package/dist/core/extensions/builtin/tool-search/service.js.map +1 -0
- package/dist/core/extensions/builtin/tool-search/tool.d.ts +21 -0
- package/dist/core/extensions/builtin/tool-search/tool.d.ts.map +1 -0
- package/dist/core/extensions/builtin/tool-search/tool.js +95 -0
- package/dist/core/extensions/builtin/tool-search/tool.js.map +1 -0
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +3 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +44 -4
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js +11 -0
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/provider-composer.d.ts +10 -1
- package/dist/core/provider-composer.d.ts.map +1 -1
- package/dist/core/provider-composer.js +9 -0
- package/dist/core/provider-composer.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +17 -5
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/modes/provider-native-rendering.d.ts.map +1 -1
- package/dist/modes/provider-native-rendering.js +33 -0
- package/dist/modes/provider-native-rendering.js.map +1 -1
- package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
- package/dist/modes/rpc/connection-handler.js +130 -4
- package/dist/modes/rpc/connection-handler.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +31 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/senpi +1 -0
- package/dist/valid-cwd.d.ts +2 -0
- package/dist/valid-cwd.d.ts.map +1 -0
- package/dist/valid-cwd.js +14 -0
- package/dist/valid-cwd.js.map +1 -0
- package/docs/environment-variables.md +7 -0
- package/docs/extensions.md +50 -13
- package/docs/mcp.md +2 -0
- package/docs/rpc.md +54 -0
- package/docs/skills.md +8 -0
- package/examples/extensions/searchable-tools.ts +49 -0
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +29 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/kernel.ts +14 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/kernel.ts +14 -1
- package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/runtime-asset.ts +31 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +5 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts +4 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +2 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +9 -0
- package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +7 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-images.d.ts +8 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-images.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-images.js +222 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-images.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-images.lazy.d.ts +3 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-images.lazy.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-images.lazy.js +10 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-images.lazy.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js +113 -4
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.d.ts +5 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.js +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/transform-messages.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/types.d.ts +10 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts +32 -0
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +32 -0
- package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.js +36 -13
- package/node_modules/@earendil-works/pi-ai/dist/models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/openai-responses-compat.d.ts +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/openai-responses-compat.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/openai-responses-compat.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/all.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/all.js +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/all.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/google.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/images/register-builtins.d.ts +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/images/register-builtins.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/images/register-builtins.js +18 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/images/register-builtins.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-images.d.ts +3 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-images.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-images.js +16 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/openai-images.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +7 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +1 -1
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +9 -9
- package/dist/core/extensions/builtin/mcp/expose/bm25.d.ts +0 -30
- package/dist/core/extensions/builtin/mcp/expose/bm25.d.ts.map +0 -1
- package/dist/core/extensions/builtin/mcp/expose/bm25.js.map +0 -1
- package/dist/core/extensions/builtin/mcp/expose/lazy-activate.d.ts +0 -15
- package/dist/core/extensions/builtin/mcp/expose/lazy-activate.d.ts.map +0 -1
- package/dist/core/extensions/builtin/mcp/expose/lazy-activate.js +0 -33
- package/dist/core/extensions/builtin/mcp/expose/lazy-activate.js.map +0 -1
- package/dist/core/extensions/builtin/mcp/expose/tool-search.d.ts +0 -43
- package/dist/core/extensions/builtin/mcp/expose/tool-search.d.ts.map +0 -1
- package/dist/core/extensions/builtin/mcp/expose/tool-search.js +0 -125
- package/dist/core/extensions/builtin/mcp/expose/tool-search.js.map +0 -1
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { displayPath, GENERATED_IMAGE_DIRECTORY, sanitizeImageStem } from "../imagegen/paths.js";
|
|
4
|
+
export const IMAGE_GENERATION_CALL_SUBTYPE = "image_generation_call";
|
|
5
|
+
const MAX_COLLISION_ATTEMPTS = 100;
|
|
6
|
+
function readString(record, key) {
|
|
7
|
+
const value = record[key];
|
|
8
|
+
return typeof value === "string" ? value : undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Recognizes a completed native image call carrying a base64 payload. Non-completed
|
|
12
|
+
* and already-scrubbed items return undefined: they carry no bytes to externalize.
|
|
13
|
+
*/
|
|
14
|
+
export function readCompletedNativeImage(raw) {
|
|
15
|
+
if (typeof raw !== "object" || raw === null)
|
|
16
|
+
return undefined;
|
|
17
|
+
const record = { ...raw };
|
|
18
|
+
if (readString(record, "type") !== IMAGE_GENERATION_CALL_SUBTYPE)
|
|
19
|
+
return undefined;
|
|
20
|
+
if (readString(record, "status") !== "completed")
|
|
21
|
+
return undefined;
|
|
22
|
+
const result = readString(record, "result");
|
|
23
|
+
if (result === undefined || result.length === 0)
|
|
24
|
+
return undefined;
|
|
25
|
+
const id = readString(record, "id");
|
|
26
|
+
const revisedPrompt = readString(record, "revised_prompt");
|
|
27
|
+
return {
|
|
28
|
+
...(id === undefined ? {} : { id }),
|
|
29
|
+
result,
|
|
30
|
+
...(revisedPrompt?.trim() ? { revisedPrompt } : {}),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Detects the container from the decoded magic bytes. The Responses API does not
|
|
35
|
+
* contractually pin the encoding of `result`, so an undetectable payload defaults
|
|
36
|
+
* to png rather than being rejected.
|
|
37
|
+
*/
|
|
38
|
+
export function detectImageExtension(bytes) {
|
|
39
|
+
if (bytes.length >= 8 &&
|
|
40
|
+
bytes.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))) {
|
|
41
|
+
return "png";
|
|
42
|
+
}
|
|
43
|
+
if (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
|
|
44
|
+
return "jpg";
|
|
45
|
+
}
|
|
46
|
+
if (bytes.length >= 12 &&
|
|
47
|
+
bytes.subarray(0, 4).toString("ascii") === "RIFF" &&
|
|
48
|
+
bytes.subarray(8, 12).toString("ascii") === "WEBP") {
|
|
49
|
+
return "webp";
|
|
50
|
+
}
|
|
51
|
+
return "png";
|
|
52
|
+
}
|
|
53
|
+
/** Item id when the provider sent one, else `<responseId>-<outputIndex>`. */
|
|
54
|
+
function fileStem(image, responseId, outputIndex) {
|
|
55
|
+
if (image.id !== undefined)
|
|
56
|
+
return sanitizeImageStem(image.id);
|
|
57
|
+
return sanitizeImageStem(`${responseId ?? "image"}-${outputIndex}`);
|
|
58
|
+
}
|
|
59
|
+
function decodeBase64(result) {
|
|
60
|
+
const bytes = Buffer.from(result, "base64");
|
|
61
|
+
if (bytes.length === 0)
|
|
62
|
+
throw new Error("decoded to zero bytes");
|
|
63
|
+
return bytes;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Writes the image without ever overwriting an existing file: a taken name gains a
|
|
67
|
+
* `-2`, `-3`, ... suffix, so a replayed or duplicated item id never discards a
|
|
68
|
+
* valid earlier image.
|
|
69
|
+
*/
|
|
70
|
+
async function writeWithoutOverwrite(directory, stem, extension, bytes) {
|
|
71
|
+
await mkdir(directory, { recursive: true });
|
|
72
|
+
for (let attempt = 1; attempt <= MAX_COLLISION_ATTEMPTS; attempt++) {
|
|
73
|
+
const suffix = attempt === 1 ? "" : `-${attempt}`;
|
|
74
|
+
const target = join(directory, `${stem}${suffix}.${extension}`);
|
|
75
|
+
try {
|
|
76
|
+
await mkdir(dirname(target), { recursive: true });
|
|
77
|
+
await writeFile(target, bytes, { flag: "wx" });
|
|
78
|
+
return target;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
const code = error instanceof Error && "code" in error ? error.code : undefined;
|
|
82
|
+
if (code !== "EEXIST")
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
throw new Error(`no free filename for ${stem}.${extension}`);
|
|
87
|
+
}
|
|
88
|
+
function successText(cwd, target, revisedPrompt) {
|
|
89
|
+
const lines = [`Generated image: ${displayPath(cwd, target)}`];
|
|
90
|
+
if (revisedPrompt !== undefined)
|
|
91
|
+
lines.push(`Revised prompt: ${revisedPrompt}`);
|
|
92
|
+
return { type: "text", text: lines.join("\n") };
|
|
93
|
+
}
|
|
94
|
+
function failureText(reason) {
|
|
95
|
+
return { type: "text", text: `Generated image could not be saved: ${reason}.` };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Externalizes one completed block. The base64 leaves the transcript whether or not
|
|
99
|
+
* the write succeeds, so a failing disk can never turn into a persisted payload.
|
|
100
|
+
*/
|
|
101
|
+
async function externalizeBlock(cwd, image, responseId, outputIndex) {
|
|
102
|
+
try {
|
|
103
|
+
const bytes = decodeBase64(image.result);
|
|
104
|
+
const target = await writeWithoutOverwrite(join(cwd, GENERATED_IMAGE_DIRECTORY), fileStem(image, responseId, outputIndex), detectImageExtension(bytes), bytes);
|
|
105
|
+
return successText(cwd, target, image.revisedPrompt);
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
return failureText(error instanceof Error ? error.message : String(error));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function isImageGenerationCall(block) {
|
|
112
|
+
return block.type === "providerNative" && block.subtype === IMAGE_GENERATION_CALL_SUBTYPE;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Replaces every completed native image block with text pointing at a saved file.
|
|
116
|
+
* Returns undefined when the message carries no image bytes, so unrelated messages
|
|
117
|
+
* pass through by reference.
|
|
118
|
+
*/
|
|
119
|
+
export async function externalizeNativeImages(message, cwd) {
|
|
120
|
+
const content = [];
|
|
121
|
+
let replaced = false;
|
|
122
|
+
for (const [outputIndex, block] of message.content.entries()) {
|
|
123
|
+
const image = isImageGenerationCall(block) ? readCompletedNativeImage(block.raw) : undefined;
|
|
124
|
+
if (image === undefined) {
|
|
125
|
+
content.push(block);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
content.push(await externalizeBlock(cwd, image, message.responseId, outputIndex));
|
|
129
|
+
replaced = true;
|
|
130
|
+
}
|
|
131
|
+
return replaced ? { ...message, content } : undefined;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=externalize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"externalize.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/openai-image-gen/externalize.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport type { AssistantMessage, ProviderNativeContent, TextContent } from \"@earendil-works/pi-ai\";\nimport { displayPath, GENERATED_IMAGE_DIRECTORY, sanitizeImageStem } from \"../imagegen/paths.ts\";\n\nexport const IMAGE_GENERATION_CALL_SUBTYPE = \"image_generation_call\";\nconst MAX_COLLISION_ATTEMPTS = 100;\n\n/** The completed shape this pass externalizes. Everything else stays untouched. */\ninterface CompletedNativeImage {\n\tid?: string;\n\tresult: string;\n\trevisedPrompt?: string;\n}\n\nfunction readString(record: Record<string, unknown>, key: string): string | undefined {\n\tconst value = record[key];\n\treturn typeof value === \"string\" ? value : undefined;\n}\n\n/**\n * Recognizes a completed native image call carrying a base64 payload. Non-completed\n * and already-scrubbed items return undefined: they carry no bytes to externalize.\n */\nexport function readCompletedNativeImage(raw: unknown): CompletedNativeImage | undefined {\n\tif (typeof raw !== \"object\" || raw === null) return undefined;\n\tconst record: Record<string, unknown> = { ...raw };\n\tif (readString(record, \"type\") !== IMAGE_GENERATION_CALL_SUBTYPE) return undefined;\n\tif (readString(record, \"status\") !== \"completed\") return undefined;\n\tconst result = readString(record, \"result\");\n\tif (result === undefined || result.length === 0) return undefined;\n\tconst id = readString(record, \"id\");\n\tconst revisedPrompt = readString(record, \"revised_prompt\");\n\treturn {\n\t\t...(id === undefined ? {} : { id }),\n\t\tresult,\n\t\t...(revisedPrompt?.trim() ? { revisedPrompt } : {}),\n\t};\n}\n\n/**\n * Detects the container from the decoded magic bytes. The Responses API does not\n * contractually pin the encoding of `result`, so an undetectable payload defaults\n * to png rather than being rejected.\n */\nexport function detectImageExtension(bytes: Buffer): \"png\" | \"jpg\" | \"webp\" {\n\tif (\n\t\tbytes.length >= 8 &&\n\t\tbytes.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))\n\t) {\n\t\treturn \"png\";\n\t}\n\tif (bytes.length >= 3 && bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {\n\t\treturn \"jpg\";\n\t}\n\tif (\n\t\tbytes.length >= 12 &&\n\t\tbytes.subarray(0, 4).toString(\"ascii\") === \"RIFF\" &&\n\t\tbytes.subarray(8, 12).toString(\"ascii\") === \"WEBP\"\n\t) {\n\t\treturn \"webp\";\n\t}\n\treturn \"png\";\n}\n\n/** Item id when the provider sent one, else `<responseId>-<outputIndex>`. */\nfunction fileStem(image: CompletedNativeImage, responseId: string | undefined, outputIndex: number): string {\n\tif (image.id !== undefined) return sanitizeImageStem(image.id);\n\treturn sanitizeImageStem(`${responseId ?? \"image\"}-${outputIndex}`);\n}\n\nfunction decodeBase64(result: string): Buffer {\n\tconst bytes = Buffer.from(result, \"base64\");\n\tif (bytes.length === 0) throw new Error(\"decoded to zero bytes\");\n\treturn bytes;\n}\n\n/**\n * Writes the image without ever overwriting an existing file: a taken name gains a\n * `-2`, `-3`, ... suffix, so a replayed or duplicated item id never discards a\n * valid earlier image.\n */\nasync function writeWithoutOverwrite(\n\tdirectory: string,\n\tstem: string,\n\textension: string,\n\tbytes: Buffer,\n): Promise<string> {\n\tawait mkdir(directory, { recursive: true });\n\tfor (let attempt = 1; attempt <= MAX_COLLISION_ATTEMPTS; attempt++) {\n\t\tconst suffix = attempt === 1 ? \"\" : `-${attempt}`;\n\t\tconst target = join(directory, `${stem}${suffix}.${extension}`);\n\t\ttry {\n\t\t\tawait mkdir(dirname(target), { recursive: true });\n\t\t\tawait writeFile(target, bytes, { flag: \"wx\" });\n\t\t\treturn target;\n\t\t} catch (error) {\n\t\t\tconst code = error instanceof Error && \"code\" in error ? error.code : undefined;\n\t\t\tif (code !== \"EEXIST\") throw error;\n\t\t}\n\t}\n\tthrow new Error(`no free filename for ${stem}.${extension}`);\n}\n\nfunction successText(cwd: string, target: string, revisedPrompt: string | undefined): TextContent {\n\tconst lines = [`Generated image: ${displayPath(cwd, target)}`];\n\tif (revisedPrompt !== undefined) lines.push(`Revised prompt: ${revisedPrompt}`);\n\treturn { type: \"text\", text: lines.join(\"\\n\") };\n}\n\nfunction failureText(reason: string): TextContent {\n\treturn { type: \"text\", text: `Generated image could not be saved: ${reason}.` };\n}\n\n/**\n * Externalizes one completed block. The base64 leaves the transcript whether or not\n * the write succeeds, so a failing disk can never turn into a persisted payload.\n */\nasync function externalizeBlock(\n\tcwd: string,\n\timage: CompletedNativeImage,\n\tresponseId: string | undefined,\n\toutputIndex: number,\n): Promise<TextContent> {\n\ttry {\n\t\tconst bytes = decodeBase64(image.result);\n\t\tconst target = await writeWithoutOverwrite(\n\t\t\tjoin(cwd, GENERATED_IMAGE_DIRECTORY),\n\t\t\tfileStem(image, responseId, outputIndex),\n\t\t\tdetectImageExtension(bytes),\n\t\t\tbytes,\n\t\t);\n\t\treturn successText(cwd, target, image.revisedPrompt);\n\t} catch (error) {\n\t\treturn failureText(error instanceof Error ? error.message : String(error));\n\t}\n}\n\nfunction isImageGenerationCall(block: { type: string; subtype?: string }): block is ProviderNativeContent {\n\treturn block.type === \"providerNative\" && block.subtype === IMAGE_GENERATION_CALL_SUBTYPE;\n}\n\n/**\n * Replaces every completed native image block with text pointing at a saved file.\n * Returns undefined when the message carries no image bytes, so unrelated messages\n * pass through by reference.\n */\nexport async function externalizeNativeImages(\n\tmessage: AssistantMessage,\n\tcwd: string,\n): Promise<AssistantMessage | undefined> {\n\tconst content: AssistantMessage[\"content\"] = [];\n\tlet replaced = false;\n\tfor (const [outputIndex, block] of message.content.entries()) {\n\t\tconst image = isImageGenerationCall(block) ? readCompletedNativeImage(block.raw) : undefined;\n\t\tif (image === undefined) {\n\t\t\tcontent.push(block);\n\t\t\tcontinue;\n\t\t}\n\t\tcontent.push(await externalizeBlock(cwd, image, message.responseId, outputIndex));\n\t\treplaced = true;\n\t}\n\treturn replaced ? { ...message, content } : undefined;\n}\n"]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
2
|
+
/** The model fields the native image-generation gate and its cache key depend on. */
|
|
3
|
+
export type NativeImageGenModel = Pick<Model<Api>, "id" | "provider" | "api" | "baseUrl" | "compat">;
|
|
4
|
+
export type NativeImageGenTarget = NativeImageGenModel | undefined;
|
|
5
|
+
/** Whether native image generation injection is enabled for this process. */
|
|
6
|
+
export declare function isOpenAiImageGenEnabled(): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Whether the model's endpoint serves the OpenAI Responses `image_generation`
|
|
9
|
+
* server tool.
|
|
10
|
+
*
|
|
11
|
+
* Unlike the web-search gate, `azure-openai-responses` defaults to FALSE: Azure
|
|
12
|
+
* deployments expose image generation as a separate deployment rather than as a
|
|
13
|
+
* Responses server tool, so azure opts in only through
|
|
14
|
+
* `compat.supportsImageGeneration`. Proxied `openai-responses` endpoints default
|
|
15
|
+
* to the client tool for the same reason they do for web search: a translating
|
|
16
|
+
* gateway rejects the tool type it never implemented.
|
|
17
|
+
*/
|
|
18
|
+
export declare function supportsNativeOpenAiImageGeneration(target: NativeImageGenTarget): boolean;
|
|
19
|
+
/** Identity of the model an arbitration decision was made for. */
|
|
20
|
+
export declare function nativeImageGenModelKey(target: NativeImageGenTarget): string;
|
|
21
|
+
//# sourceMappingURL=gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/openai-image-gen/gate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAExD,qFAAqF;AACrF,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,UAAU,GAAG,KAAK,GAAG,SAAS,GAAG,QAAQ,CAAC,CAAC;AACrG,MAAM,MAAM,oBAAoB,GAAG,mBAAmB,GAAG,SAAS,CAAC;AAyBnE,6EAA6E;AAC7E,wBAAgB,uBAAuB,IAAI,OAAO,CAEjD;AAmBD;;;;;;;;;;GAUG;AACH,wBAAgB,mCAAmC,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAOzF;AAED,kEAAkE;AAClE,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,oBAAoB,GAAG,MAAM,CAG3E"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const ENABLE_ENV = "PI_OPENAI_IMAGE_GEN";
|
|
2
|
+
const OFFICIAL_OPENAI_HOST = "api.openai.com";
|
|
3
|
+
const OFFICIAL_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
4
|
+
function parseEnableEnv(envVar) {
|
|
5
|
+
const envValue = process.env[envVar];
|
|
6
|
+
if (!envValue) {
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
const normalized = envValue.trim().toLowerCase();
|
|
10
|
+
if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
// Unknown values fall back to default-on behavior.
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
/** Whether native image generation injection is enabled for this process. */
|
|
20
|
+
export function isOpenAiImageGenEnabled() {
|
|
21
|
+
return parseEnableEnv(ENABLE_ENV);
|
|
22
|
+
}
|
|
23
|
+
/** Reads `compat.supportsImageGeneration` without narrowing the per-api compat union by cast. */
|
|
24
|
+
function compatImageGenerationOverride(compat) {
|
|
25
|
+
if (typeof compat !== "object" || compat === null || !("supportsImageGeneration" in compat)) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
const value = compat.supportsImageGeneration;
|
|
29
|
+
return typeof value === "boolean" ? value : undefined;
|
|
30
|
+
}
|
|
31
|
+
function isOfficialOpenAiEndpoint(baseUrl) {
|
|
32
|
+
try {
|
|
33
|
+
return new URL(baseUrl || OFFICIAL_OPENAI_BASE_URL).hostname === OFFICIAL_OPENAI_HOST;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Whether the model's endpoint serves the OpenAI Responses `image_generation`
|
|
41
|
+
* server tool.
|
|
42
|
+
*
|
|
43
|
+
* Unlike the web-search gate, `azure-openai-responses` defaults to FALSE: Azure
|
|
44
|
+
* deployments expose image generation as a separate deployment rather than as a
|
|
45
|
+
* Responses server tool, so azure opts in only through
|
|
46
|
+
* `compat.supportsImageGeneration`. Proxied `openai-responses` endpoints default
|
|
47
|
+
* to the client tool for the same reason they do for web search: a translating
|
|
48
|
+
* gateway rejects the tool type it never implemented.
|
|
49
|
+
*/
|
|
50
|
+
export function supportsNativeOpenAiImageGeneration(target) {
|
|
51
|
+
if (target === undefined || target.api !== "openai-responses") {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const override = compatImageGenerationOverride(target.compat);
|
|
55
|
+
return override ?? isOfficialOpenAiEndpoint(target.baseUrl);
|
|
56
|
+
}
|
|
57
|
+
/** Identity of the model an arbitration decision was made for. */
|
|
58
|
+
export function nativeImageGenModelKey(target) {
|
|
59
|
+
if (target === undefined)
|
|
60
|
+
return "";
|
|
61
|
+
return `${target.provider}|${target.api}|${target.baseUrl}|${target.id}`;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/openai-image-gen/gate.ts"],"names":[],"mappings":"AAMA,MAAM,UAAU,GAAG,qBAAqB,CAAC;AACzC,MAAM,oBAAoB,GAAG,gBAAgB,CAAC;AAC9C,MAAM,wBAAwB,GAAG,2BAA2B,CAAC;AAE7D,SAAS,cAAc,CAAC,MAAc;IACrC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACrC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACf,OAAO,IAAI,CAAC;IACb,CAAC;IAED,MAAM,UAAU,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACjD,IAAI,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,OAAO,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,KAAK,KAAK,EAAE,CAAC;QACjG,OAAO,KAAK,CAAC;IACd,CAAC;IAED,IAAI,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,MAAM,IAAI,UAAU,KAAK,KAAK,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QAChG,OAAO,IAAI,CAAC;IACb,CAAC;IAED,mDAAmD;IACnD,OAAO,IAAI,CAAC;AACb,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,uBAAuB;IACtC,OAAO,cAAc,CAAC,UAAU,CAAC,CAAC;AACnC,CAAC;AAED,iGAAiG;AACjG,SAAS,6BAA6B,CAAC,MAAe;IACrD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,CAAC,yBAAyB,IAAI,MAAM,CAAC,EAAE,CAAC;QAC7F,OAAO,SAAS,CAAC;IAClB,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,uBAAuB,CAAC;IAC7C,OAAO,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACvD,CAAC;AAED,SAAS,wBAAwB,CAAC,OAAe;IAChD,IAAI,CAAC;QACJ,OAAO,IAAI,GAAG,CAAC,OAAO,IAAI,wBAAwB,CAAC,CAAC,QAAQ,KAAK,oBAAoB,CAAC;IACvF,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,KAAK,CAAC;IACd,CAAC;AACF,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mCAAmC,CAAC,MAA4B;IAC/E,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,GAAG,KAAK,kBAAkB,EAAE,CAAC;QAC/D,OAAO,KAAK,CAAC;IACd,CAAC;IAED,MAAM,QAAQ,GAAG,6BAA6B,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9D,OAAO,QAAQ,IAAI,wBAAwB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAC7D,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,sBAAsB,CAAC,MAA4B;IAClE,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACpC,OAAO,GAAG,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,OAAO,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;AAC1E,CAAC","sourcesContent":["import type { Api, Model } from \"@earendil-works/pi-ai\";\n\n/** The model fields the native image-generation gate and its cache key depend on. */\nexport type NativeImageGenModel = Pick<Model<Api>, \"id\" | \"provider\" | \"api\" | \"baseUrl\" | \"compat\">;\nexport type NativeImageGenTarget = NativeImageGenModel | undefined;\n\nconst ENABLE_ENV = \"PI_OPENAI_IMAGE_GEN\";\nconst OFFICIAL_OPENAI_HOST = \"api.openai.com\";\nconst OFFICIAL_OPENAI_BASE_URL = \"https://api.openai.com/v1\";\n\nfunction parseEnableEnv(envVar: string): boolean {\n\tconst envValue = process.env[envVar];\n\tif (!envValue) {\n\t\treturn true;\n\t}\n\n\tconst normalized = envValue.trim().toLowerCase();\n\tif (normalized === \"0\" || normalized === \"false\" || normalized === \"no\" || normalized === \"off\") {\n\t\treturn false;\n\t}\n\n\tif (normalized === \"1\" || normalized === \"true\" || normalized === \"yes\" || normalized === \"on\") {\n\t\treturn true;\n\t}\n\n\t// Unknown values fall back to default-on behavior.\n\treturn true;\n}\n\n/** Whether native image generation injection is enabled for this process. */\nexport function isOpenAiImageGenEnabled(): boolean {\n\treturn parseEnableEnv(ENABLE_ENV);\n}\n\n/** Reads `compat.supportsImageGeneration` without narrowing the per-api compat union by cast. */\nfunction compatImageGenerationOverride(compat: unknown): boolean | undefined {\n\tif (typeof compat !== \"object\" || compat === null || !(\"supportsImageGeneration\" in compat)) {\n\t\treturn undefined;\n\t}\n\tconst value = compat.supportsImageGeneration;\n\treturn typeof value === \"boolean\" ? value : undefined;\n}\n\nfunction isOfficialOpenAiEndpoint(baseUrl: string): boolean {\n\ttry {\n\t\treturn new URL(baseUrl || OFFICIAL_OPENAI_BASE_URL).hostname === OFFICIAL_OPENAI_HOST;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Whether the model's endpoint serves the OpenAI Responses `image_generation`\n * server tool.\n *\n * Unlike the web-search gate, `azure-openai-responses` defaults to FALSE: Azure\n * deployments expose image generation as a separate deployment rather than as a\n * Responses server tool, so azure opts in only through\n * `compat.supportsImageGeneration`. Proxied `openai-responses` endpoints default\n * to the client tool for the same reason they do for web search: a translating\n * gateway rejects the tool type it never implemented.\n */\nexport function supportsNativeOpenAiImageGeneration(target: NativeImageGenTarget): boolean {\n\tif (target === undefined || target.api !== \"openai-responses\") {\n\t\treturn false;\n\t}\n\n\tconst override = compatImageGenerationOverride(target.compat);\n\treturn override ?? isOfficialOpenAiEndpoint(target.baseUrl);\n}\n\n/** Identity of the model an arbitration decision was made for. */\nexport function nativeImageGenModelKey(target: NativeImageGenTarget): string {\n\tif (target === undefined) return \"\";\n\treturn `${target.provider}|${target.api}|${target.baseUrl}|${target.id}`;\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "../../types.ts";
|
|
2
|
+
import { type ImageGenMode } from "./inject.ts";
|
|
3
|
+
export { isOpenAiImageGenEnabled, supportsNativeOpenAiImageGeneration } from "./gate.ts";
|
|
4
|
+
export { applyImageGenerationTools, NATIVE_IMAGE_GEN_TOOL_TYPE } from "./inject.ts";
|
|
5
|
+
/** Arbitration decision for one model. `source`/`reason` are diagnostics only. */
|
|
6
|
+
export interface ImageGenArbitrationState {
|
|
7
|
+
kind: ImageGenMode;
|
|
8
|
+
modelKey: string;
|
|
9
|
+
source?: string;
|
|
10
|
+
reason?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const OPENAI_IMAGE_GEN_SECTION = "\n## Image Generation\n\nNative image generation is available in this session.\nGenerate images with the built-in image_generation tool instead of a client-side tool.\n";
|
|
13
|
+
export default function openaiImageGenExtension(pi: ExtensionAPI): void;
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/openai-image-gen/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAoB,MAAM,gBAAgB,CAAC;AAUrE,OAAO,EAA6B,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3E,OAAO,EAAE,uBAAuB,EAAE,mCAAmC,EAAE,MAAM,WAAW,CAAC;AACzF,OAAO,EAAE,yBAAyB,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAEpF,kFAAkF;AAClF,MAAM,WAAW,wBAAwB;IACxC,IAAI,EAAE,YAAY,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,wBAAwB,6KAKpC,CAAC;AAoBF,MAAM,CAAC,OAAO,UAAU,uBAAuB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAqDtE"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { resolveImageGenAuth } from "../imagegen/auth.js";
|
|
2
|
+
import { imageGenRegistryOverride, setNativeBypass } from "../imagegen/state.js";
|
|
3
|
+
import { externalizeNativeImages } from "./externalize.js";
|
|
4
|
+
import { isOpenAiImageGenEnabled, nativeImageGenModelKey, supportsNativeOpenAiImageGeneration, } from "./gate.js";
|
|
5
|
+
import { applyImageGenerationTools } from "./inject.js";
|
|
6
|
+
export { isOpenAiImageGenEnabled, supportsNativeOpenAiImageGeneration } from "./gate.js";
|
|
7
|
+
export { applyImageGenerationTools, NATIVE_IMAGE_GEN_TOOL_TYPE } from "./inject.js";
|
|
8
|
+
export const OPENAI_IMAGE_GEN_SECTION = `
|
|
9
|
+
## Image Generation
|
|
10
|
+
|
|
11
|
+
Native image generation is available in this session.
|
|
12
|
+
Generate images with the built-in image_generation tool instead of a client-side tool.
|
|
13
|
+
`;
|
|
14
|
+
const UNKNOWN_MODEL_REASON = "No model is selected, so image generation availability is unknown.";
|
|
15
|
+
async function resolveState(model, ctx) {
|
|
16
|
+
const modelKey = nativeImageGenModelKey(model);
|
|
17
|
+
if (model === undefined) {
|
|
18
|
+
return { kind: "unavailable", modelKey, reason: UNKNOWN_MODEL_REASON };
|
|
19
|
+
}
|
|
20
|
+
if (supportsNativeOpenAiImageGeneration(model) && isOpenAiImageGenEnabled()) {
|
|
21
|
+
return { kind: "native", modelKey, source: model.baseUrl };
|
|
22
|
+
}
|
|
23
|
+
const auth = await resolveImageGenAuth({ modelRegistry: imageGenRegistryOverride() ?? ctx.modelRegistry });
|
|
24
|
+
if (auth.kind !== "none") {
|
|
25
|
+
return { kind: "client", modelKey, source: `${auth.provenance}:${auth.providerId ?? auth.kind}` };
|
|
26
|
+
}
|
|
27
|
+
return { kind: "unavailable", modelKey, reason: auth.reason };
|
|
28
|
+
}
|
|
29
|
+
export default function openaiImageGenExtension(pi) {
|
|
30
|
+
let state = { kind: "unavailable", modelKey: "", reason: UNKNOWN_MODEL_REASON };
|
|
31
|
+
async function refresh(model, ctx) {
|
|
32
|
+
state = await resolveState(model, ctx);
|
|
33
|
+
// Cross-builtin wiring: the client tool defers only while this builtin will
|
|
34
|
+
// actually inject the server tool for this exact model. Both flip directions
|
|
35
|
+
// run through here, so a switch back to a proxied endpoint re-arms the tool.
|
|
36
|
+
setNativeBypass(state.kind === "native");
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Payload hooks observe the effective request model, which can differ from the
|
|
40
|
+
* model the last lifecycle event cached (fallback routing, per-request model
|
|
41
|
+
* resolution). Refresh before mutating so a stale decision never reaches the wire.
|
|
42
|
+
*/
|
|
43
|
+
async function ensureFresh(model, ctx) {
|
|
44
|
+
if (nativeImageGenModelKey(model) !== state.modelKey) {
|
|
45
|
+
await refresh(model, ctx);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
49
|
+
await refresh(ctx.model, ctx);
|
|
50
|
+
});
|
|
51
|
+
pi.on("model_select", async (event, ctx) => {
|
|
52
|
+
await refresh(event.model, ctx);
|
|
53
|
+
});
|
|
54
|
+
pi.on("before_provider_request", async (event, ctx) => {
|
|
55
|
+
const model = event.model ?? ctx.model;
|
|
56
|
+
await ensureFresh(model, ctx);
|
|
57
|
+
return applyImageGenerationTools(event.payload, state.kind);
|
|
58
|
+
});
|
|
59
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
60
|
+
await ensureFresh(ctx.model, ctx);
|
|
61
|
+
if (state.kind !== "native")
|
|
62
|
+
return undefined;
|
|
63
|
+
return { systemPrompt: `${event.systemPrompt}\n${OPENAI_IMAGE_GEN_SECTION}` };
|
|
64
|
+
});
|
|
65
|
+
// Runs for every assistant message regardless of arbitration state: whichever
|
|
66
|
+
// path produced the bytes, they must never reach session history.
|
|
67
|
+
pi.on("message_end", async (event, ctx) => {
|
|
68
|
+
if (event.message.role !== "assistant")
|
|
69
|
+
return undefined;
|
|
70
|
+
const message = await externalizeNativeImages(event.message, ctx.cwd);
|
|
71
|
+
return message === undefined ? undefined : { message };
|
|
72
|
+
});
|
|
73
|
+
pi.on("session_shutdown", async () => {
|
|
74
|
+
setNativeBypass(false);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/openai-image-gen/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ExtensionAPI, ExtensionContext } from \"../../types.ts\";\nimport { resolveImageGenAuth } from \"../imagegen/auth.ts\";\nimport { imageGenRegistryOverride, setNativeBypass } from \"../imagegen/state.ts\";\nimport { externalizeNativeImages } from \"./externalize.ts\";\nimport {\n\tisOpenAiImageGenEnabled,\n\ttype NativeImageGenTarget,\n\tnativeImageGenModelKey,\n\tsupportsNativeOpenAiImageGeneration,\n} from \"./gate.ts\";\nimport { applyImageGenerationTools, type ImageGenMode } from \"./inject.ts\";\n\nexport { isOpenAiImageGenEnabled, supportsNativeOpenAiImageGeneration } from \"./gate.ts\";\nexport { applyImageGenerationTools, NATIVE_IMAGE_GEN_TOOL_TYPE } from \"./inject.ts\";\n\n/** Arbitration decision for one model. `source`/`reason` are diagnostics only. */\nexport interface ImageGenArbitrationState {\n\tkind: ImageGenMode;\n\tmodelKey: string;\n\tsource?: string;\n\treason?: string;\n}\n\nexport const OPENAI_IMAGE_GEN_SECTION = `\n## Image Generation\n\nNative image generation is available in this session.\nGenerate images with the built-in image_generation tool instead of a client-side tool.\n`;\n\nconst UNKNOWN_MODEL_REASON = \"No model is selected, so image generation availability is unknown.\";\n\nasync function resolveState(model: NativeImageGenTarget, ctx: ExtensionContext): Promise<ImageGenArbitrationState> {\n\tconst modelKey = nativeImageGenModelKey(model);\n\tif (model === undefined) {\n\t\treturn { kind: \"unavailable\", modelKey, reason: UNKNOWN_MODEL_REASON };\n\t}\n\tif (supportsNativeOpenAiImageGeneration(model) && isOpenAiImageGenEnabled()) {\n\t\treturn { kind: \"native\", modelKey, source: model.baseUrl };\n\t}\n\n\tconst auth = await resolveImageGenAuth({ modelRegistry: imageGenRegistryOverride() ?? ctx.modelRegistry });\n\tif (auth.kind !== \"none\") {\n\t\treturn { kind: \"client\", modelKey, source: `${auth.provenance}:${auth.providerId ?? auth.kind}` };\n\t}\n\treturn { kind: \"unavailable\", modelKey, reason: auth.reason };\n}\n\nexport default function openaiImageGenExtension(pi: ExtensionAPI): void {\n\tlet state: ImageGenArbitrationState = { kind: \"unavailable\", modelKey: \"\", reason: UNKNOWN_MODEL_REASON };\n\n\tasync function refresh(model: NativeImageGenTarget, ctx: ExtensionContext): Promise<void> {\n\t\tstate = await resolveState(model, ctx);\n\t\t// Cross-builtin wiring: the client tool defers only while this builtin will\n\t\t// actually inject the server tool for this exact model. Both flip directions\n\t\t// run through here, so a switch back to a proxied endpoint re-arms the tool.\n\t\tsetNativeBypass(state.kind === \"native\");\n\t}\n\n\t/**\n\t * Payload hooks observe the effective request model, which can differ from the\n\t * model the last lifecycle event cached (fallback routing, per-request model\n\t * resolution). Refresh before mutating so a stale decision never reaches the wire.\n\t */\n\tasync function ensureFresh(model: NativeImageGenTarget, ctx: ExtensionContext): Promise<void> {\n\t\tif (nativeImageGenModelKey(model) !== state.modelKey) {\n\t\t\tawait refresh(model, ctx);\n\t\t}\n\t}\n\n\tpi.on(\"session_start\", async (_event, ctx) => {\n\t\tawait refresh(ctx.model, ctx);\n\t});\n\n\tpi.on(\"model_select\", async (event, ctx) => {\n\t\tawait refresh(event.model, ctx);\n\t});\n\n\tpi.on(\"before_provider_request\", async (event, ctx) => {\n\t\tconst model = event.model ?? ctx.model;\n\t\tawait ensureFresh(model, ctx);\n\t\treturn applyImageGenerationTools(event.payload, state.kind);\n\t});\n\n\tpi.on(\"before_agent_start\", async (event, ctx) => {\n\t\tawait ensureFresh(ctx.model, ctx);\n\t\tif (state.kind !== \"native\") return undefined;\n\t\treturn { systemPrompt: `${event.systemPrompt}\\n${OPENAI_IMAGE_GEN_SECTION}` };\n\t});\n\n\t// Runs for every assistant message regardless of arbitration state: whichever\n\t// path produced the bytes, they must never reach session history.\n\tpi.on(\"message_end\", async (event, ctx) => {\n\t\tif (event.message.role !== \"assistant\") return undefined;\n\t\tconst message = await externalizeNativeImages(event.message, ctx.cwd);\n\t\treturn message === undefined ? undefined : { message };\n\t});\n\n\tpi.on(\"session_shutdown\", async () => {\n\t\tsetNativeBypass(false);\n\t});\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Which image-generation surface owns the current request. */
|
|
2
|
+
export type ImageGenMode = "native" | "client" | "unavailable";
|
|
3
|
+
export declare const NATIVE_IMAGE_GEN_TOOL_TYPE = "image_generation";
|
|
4
|
+
/**
|
|
5
|
+
* Enforces mutual exclusion between the client `generate_image` function tool
|
|
6
|
+
* and the OpenAI Responses `image_generation` server tool at the wire level.
|
|
7
|
+
*
|
|
8
|
+
* Native entries are always removed first, so a payload that already carries one
|
|
9
|
+
* (a replayed request, a second injector, a dated tool variant) can never end up
|
|
10
|
+
* with duplicates and never leaks to a backend that does not implement it. When
|
|
11
|
+
* nothing changes the ORIGINAL payload reference is returned, because payload
|
|
12
|
+
* hooks chain and an unconditional copy would defeat identity checks downstream.
|
|
13
|
+
*/
|
|
14
|
+
export declare function applyImageGenerationTools(payload: unknown, mode: ImageGenMode): unknown;
|
|
15
|
+
//# sourceMappingURL=inject.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inject.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/openai-image-gen/inject.ts"],"names":[],"mappings":"AAEA,+DAA+D;AAC/D,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,aAAa,CAAC;AAE/D,eAAO,MAAM,0BAA0B,qBAAqB,CAAC;AAuB7D;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAevF"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { GENERATE_IMAGE_TOOL_NAME } from "../imagegen/tool.js";
|
|
2
|
+
export const NATIVE_IMAGE_GEN_TOOL_TYPE = "image_generation";
|
|
3
|
+
function isRecord(value) {
|
|
4
|
+
return typeof value === "object" && value !== null;
|
|
5
|
+
}
|
|
6
|
+
function isNativeImageGenTool(tool) {
|
|
7
|
+
if (!isRecord(tool))
|
|
8
|
+
return false;
|
|
9
|
+
const type = tool.type;
|
|
10
|
+
return (typeof type === "string" &&
|
|
11
|
+
(type === NATIVE_IMAGE_GEN_TOOL_TYPE || type.startsWith(`${NATIVE_IMAGE_GEN_TOOL_TYPE}_`)));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Matches the client tool by name rather than by `type`: extension and faux
|
|
15
|
+
* payloads describe function tools without a `type` discriminator.
|
|
16
|
+
*/
|
|
17
|
+
function isGenerateImageFunctionTool(tool) {
|
|
18
|
+
return isRecord(tool) && tool.name === GENERATE_IMAGE_TOOL_NAME;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Enforces mutual exclusion between the client `generate_image` function tool
|
|
22
|
+
* and the OpenAI Responses `image_generation` server tool at the wire level.
|
|
23
|
+
*
|
|
24
|
+
* Native entries are always removed first, so a payload that already carries one
|
|
25
|
+
* (a replayed request, a second injector, a dated tool variant) can never end up
|
|
26
|
+
* with duplicates and never leaks to a backend that does not implement it. When
|
|
27
|
+
* nothing changes the ORIGINAL payload reference is returned, because payload
|
|
28
|
+
* hooks chain and an unconditional copy would defeat identity checks downstream.
|
|
29
|
+
*/
|
|
30
|
+
export function applyImageGenerationTools(payload, mode) {
|
|
31
|
+
if (!isRecord(payload))
|
|
32
|
+
return payload;
|
|
33
|
+
const tools = Array.isArray(payload.tools) ? payload.tools : undefined;
|
|
34
|
+
const stripFunctionTool = mode !== "client";
|
|
35
|
+
const kept = (tools ?? []).filter((tool) => !isNativeImageGenTool(tool) && !(stripFunctionTool && isGenerateImageFunctionTool(tool)));
|
|
36
|
+
const removed = (tools?.length ?? 0) !== kept.length;
|
|
37
|
+
if (mode !== "native") {
|
|
38
|
+
return removed ? { ...payload, tools: kept } : payload;
|
|
39
|
+
}
|
|
40
|
+
return { ...payload, tools: [...kept, { type: NATIVE_IMAGE_GEN_TOOL_TYPE }] };
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=inject.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inject.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/openai-image-gen/inject.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAK/D,MAAM,CAAC,MAAM,0BAA0B,GAAG,kBAAkB,CAAC;AAE7D,SAAS,QAAQ,CAAC,KAAc;IAC/B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AACpD,CAAC;AAED,SAAS,oBAAoB,CAAC,IAAa;IAC1C,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAClC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACvB,OAAO,CACN,OAAO,IAAI,KAAK,QAAQ;QACxB,CAAC,IAAI,KAAK,0BAA0B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,0BAA0B,GAAG,CAAC,CAAC,CAC1F,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,2BAA2B,CAAC,IAAa;IACjD,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,wBAAwB,CAAC;AACjE,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,yBAAyB,CAAC,OAAgB,EAAE,IAAkB;IAC7E,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IAEvC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IACvE,MAAM,iBAAiB,GAAG,IAAI,KAAK,QAAQ,CAAC;IAC5C,MAAM,IAAI,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,CAChC,CAAC,IAAa,EAAE,EAAE,CAAC,CAAC,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,iBAAiB,IAAI,2BAA2B,CAAC,IAAI,CAAC,CAAC,CAC3G,CAAC;IACF,MAAM,OAAO,GAAG,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC;IAErD,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;QACvB,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IACxD,CAAC;IAED,OAAO,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,0BAA0B,EAAE,CAAC,EAAE,CAAC;AAC/E,CAAC","sourcesContent":["import { GENERATE_IMAGE_TOOL_NAME } from \"../imagegen/tool.ts\";\n\n/** Which image-generation surface owns the current request. */\nexport type ImageGenMode = \"native\" | \"client\" | \"unavailable\";\n\nexport const NATIVE_IMAGE_GEN_TOOL_TYPE = \"image_generation\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction isNativeImageGenTool(tool: unknown): boolean {\n\tif (!isRecord(tool)) return false;\n\tconst type = tool.type;\n\treturn (\n\t\ttypeof type === \"string\" &&\n\t\t(type === NATIVE_IMAGE_GEN_TOOL_TYPE || type.startsWith(`${NATIVE_IMAGE_GEN_TOOL_TYPE}_`))\n\t);\n}\n\n/**\n * Matches the client tool by name rather than by `type`: extension and faux\n * payloads describe function tools without a `type` discriminator.\n */\nfunction isGenerateImageFunctionTool(tool: unknown): boolean {\n\treturn isRecord(tool) && tool.name === GENERATE_IMAGE_TOOL_NAME;\n}\n\n/**\n * Enforces mutual exclusion between the client `generate_image` function tool\n * and the OpenAI Responses `image_generation` server tool at the wire level.\n *\n * Native entries are always removed first, so a payload that already carries one\n * (a replayed request, a second injector, a dated tool variant) can never end up\n * with duplicates and never leaks to a backend that does not implement it. When\n * nothing changes the ORIGINAL payload reference is returned, because payload\n * hooks chain and an unconditional copy would defeat identity checks downstream.\n */\nexport function applyImageGenerationTools(payload: unknown, mode: ImageGenMode): unknown {\n\tif (!isRecord(payload)) return payload;\n\n\tconst tools = Array.isArray(payload.tools) ? payload.tools : undefined;\n\tconst stripFunctionTool = mode !== \"client\";\n\tconst kept = (tools ?? []).filter(\n\t\t(tool: unknown) => !isNativeImageGenTool(tool) && !(stripFunctionTool && isGenerateImageFunctionTool(tool)),\n\t);\n\tconst removed = (tools?.length ?? 0) !== kept.length;\n\n\tif (mode !== \"native\") {\n\t\treturn removed ? { ...payload, tools: kept } : payload;\n\t}\n\n\treturn { ...payload, tools: [...kept, { type: NATIVE_IMAGE_GEN_TOOL_TYPE }] };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"monitor-notify.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/terminal/monitor-notify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAmC,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEzF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"monitor-notify.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/terminal/monitor-notify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAmC,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEzF,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAQ7D,MAAM,WAAW,4BAA4B;IAC5C,GAAG,IAAI,MAAM,CAAC;IACd,UAAU,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3D,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;CACnC;AAQD,MAAM,WAAW,mBAAoB,SAAQ,oBAAoB;IAChE,QAAQ,CAAC,WAAW,EAAE,MAAM,uBAAuB,CAAC;IACpD,sEAAsE;IACtE,QAAQ,CAAC,aAAa,EAAE,MAAM,SAAS,MAAM,EAAE,CAAC;IAChD,QAAQ,CAAC,SAAS,CAAC,EAAE,4BAA4B,CAAC;CAClD;AA+CD;;;GAGG;AACH,qBAAa,eAAe;;IAc3B,YAAY,IAAI,EAAE,mBAAmB,EAGpC;IAED,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAkBrC;IAED,wFAAwF;IACxF,YAAY,IAAI,IAAI,CAEnB;IAED,6FAA6F;IAC7F,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAKtB;IAED,OAAO,IAAI,IAAI,CAQd;CA+ID"}
|
|
@@ -4,6 +4,7 @@ const SYSTEM_REMINDER_OPEN = "<system-reminder>";
|
|
|
4
4
|
const SYSTEM_REMINDER_CLOSE = "</system-reminder>";
|
|
5
5
|
const QUEUE_OVERHEAD_CHARS = 512;
|
|
6
6
|
const MONITOR_NOTIFICATION_CUSTOM_TYPE = "senpi-monitor:notification";
|
|
7
|
+
const WAKE_STREAK_QUIET_GAP_MULTIPLIER = 2;
|
|
7
8
|
const systemScheduler = {
|
|
8
9
|
now: () => Date.now(),
|
|
9
10
|
setTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
|
|
@@ -62,6 +63,7 @@ export class MonitorNotifier {
|
|
|
62
63
|
#timer;
|
|
63
64
|
#scheduledAt;
|
|
64
65
|
#consecutiveWakes = 0;
|
|
66
|
+
#lastWakeAt;
|
|
65
67
|
#wakeBudgetPaused = false;
|
|
66
68
|
constructor(deps) {
|
|
67
69
|
this.#deps = deps;
|
|
@@ -163,13 +165,18 @@ export class MonitorNotifier {
|
|
|
163
165
|
const overflowCount = [...this.#overflow.values()]
|
|
164
166
|
.filter((overflow) => injectedIds.has(overflow.id))
|
|
165
167
|
.reduce((total, overflow) => total + overflow.count, 0);
|
|
168
|
+
if (this.#lastWakeAt !== undefined &&
|
|
169
|
+
now - this.#lastWakeAt > settings.rateLimitMs * WAKE_STREAK_QUIET_GAP_MULTIPLIER) {
|
|
170
|
+
this.#consecutiveWakes = 0;
|
|
171
|
+
}
|
|
166
172
|
const deliversSummary = selected.some((event) => event.type === "summary");
|
|
167
173
|
const reachesBudget = !deliversSummary && this.#consecutiveWakes + 1 >= settings.wakeBudget;
|
|
168
174
|
const pauseNotice = reachesBudget
|
|
169
175
|
? "Monitor paused after repeated updates. Completion still wakes this session; peek bash_output or re-arm only for intermediate events."
|
|
170
176
|
: "";
|
|
171
177
|
const content = this.#buildMessage(selected, overflowCount, pauseNotice, settings.maxCharsPerInjection);
|
|
172
|
-
delivery.send(content);
|
|
178
|
+
delivery.send(content, reachesBudget ? { forceWake: true } : undefined);
|
|
179
|
+
this.#lastWakeAt = now;
|
|
173
180
|
for (const id of injectedIds) {
|
|
174
181
|
this.#lastInjectionAt.set(id, now);
|
|
175
182
|
this.#overflow.delete(id);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"monitor-notify.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/terminal/monitor-notify.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MonitorEvent } from \"./monitor-registry.ts\";\nimport { getTerminalNotificationDelivery, type TerminalNotifierDeps } from \"./notify.ts\";\nimport { sanitizeTerminalOutput } from \"./output-format.ts\";\nimport type { MonitorDeliverySettings } from \"./settings.ts\";\n\nconst SYSTEM_REMINDER_OPEN = \"<system-reminder>\";\nconst SYSTEM_REMINDER_CLOSE = \"</system-reminder>\";\nconst QUEUE_OVERHEAD_CHARS = 512;\nconst MONITOR_NOTIFICATION_CUSTOM_TYPE = \"senpi-monitor:notification\";\n\nexport interface MonitorNotificationScheduler {\n\tnow(): number;\n\tsetTimeout(callback: () => void, delayMs: number): unknown;\n\tclearTimeout(timer: unknown): void;\n}\n\nconst systemScheduler: MonitorNotificationScheduler = {\n\tnow: () => Date.now(),\n\tsetTimeout: (callback, delayMs) => setTimeout(callback, delayMs),\n\tclearTimeout: (timer) => clearTimeout(timer as ReturnType<typeof setTimeout>),\n};\n\nexport interface MonitorNotifierDeps extends TerminalNotifierDeps {\n\treadonly getSettings: () => MonitorDeliverySettings;\n\t/** Pause live monitors when their shared wake budget is exhausted. */\n\treadonly pauseMonitors: () => readonly string[];\n\treadonly scheduler?: MonitorNotificationScheduler;\n}\n\ninterface Overflow {\n\treadonly id: string;\n\tcount: number;\n}\n\nfunction boundedPositiveInt(value: number, fallback: number, minimum: number, maximum: number): number {\n\tif (!Number.isFinite(value)) return fallback;\n\treturn Math.min(Math.max(Math.trunc(value), minimum), maximum);\n}\n\nfunction resolveSettings(settings: MonitorDeliverySettings): MonitorDeliverySettings {\n\treturn {\n\t\tcoalesceWindowMs: boundedPositiveInt(settings.coalesceWindowMs, 2000, 1, 60_000),\n\t\trateLimitMs: boundedPositiveInt(settings.rateLimitMs, 5000, 1, 3_600_000),\n\t\tmaxLinesPerInjection: boundedPositiveInt(settings.maxLinesPerInjection, 50, 1, 200),\n\t\tmaxCharsPerInjection: boundedPositiveInt(settings.maxCharsPerInjection, 4096, 512, 16_384),\n\t\twakeBudget: boundedPositiveInt(settings.wakeBudget, 5, 1, 100),\n\t};\n}\n\nfunction eventBody(event: MonitorEvent): string {\n\tconst value = event.type === \"line\" ? event.line : event.summary;\n\treturn sanitizeTerminalOutput(value)\n\t\t.replace(/[\\r\\n]+/g, \" \")\n\t\t.trimEnd();\n}\n\nfunction clip(text: string, maxChars: number): string {\n\tif (text.length <= maxChars) return text;\n\tif (maxChars <= 3) return text.slice(0, maxChars);\n\treturn `${text.slice(0, maxChars - 3)}...`;\n}\n\nfunction formatEvents(events: readonly MonitorEvent[]): string {\n\tconst groups = new Map<string, { description: string; bodies: string[] }>();\n\tfor (const event of events) {\n\t\tconst group = groups.get(event.id) ?? { description: event.description, bodies: [] };\n\t\tgroup.bodies.push(eventBody(event));\n\t\tgroups.set(event.id, group);\n\t}\n\treturn [...groups.values()]\n\t\t.map((group) => `Monitor event(${group.description}): ${group.bodies.join(\"\\n\")}`)\n\t\t.join(\"\\n\");\n}\n\n/**\n * Session-scoped monitor delivery queue. It preserves the terminal runtime as the authoritative,\n * bounded event history while retaining only one capped coalescing batch for chat injection.\n */\nexport class MonitorNotifier {\n\treadonly #deps: MonitorNotifierDeps;\n\treadonly #scheduler: MonitorNotificationScheduler;\n\t#events: MonitorEvent[] = [];\n\t#eventChars = 0;\n\t#overflow = new Map<string, Overflow>();\n\t#lastInjectionAt = new Map<string, number>();\n\t#lastInjectedBatch = new Map<string, string>();\n\t#timer: unknown;\n\t#scheduledAt: number | undefined;\n\t#consecutiveWakes = 0;\n\t#wakeBudgetPaused = false;\n\n\tconstructor(deps: MonitorNotifierDeps) {\n\t\tthis.#deps = deps;\n\t\tthis.#scheduler = deps.scheduler ?? systemScheduler;\n\t}\n\n\tnotifyEvent(event: MonitorEvent): void {\n\t\tif (this.#wakeBudgetPaused) {\n\t\t\tif (event.type === \"line\") return;\n\t\t\tthis.#wakeBudgetPaused = false;\n\t\t\tthis.#consecutiveWakes = 0;\n\t\t\tthis.#lastInjectionAt.delete(event.id);\n\t\t}\n\t\tif (!getTerminalNotificationDelivery(this.#deps, MONITOR_NOTIFICATION_CUSTOM_TYPE)) return;\n\t\tconst settings = resolveSettings(this.#deps.getSettings());\n\t\tconst rendered = `Monitor event(${event.description}): ${eventBody(event)}`;\n\t\tconst queueLimit = Math.max(1, settings.maxCharsPerInjection - QUEUE_OVERHEAD_CHARS);\n\t\tif (this.#events.length >= settings.maxLinesPerInjection || this.#eventChars + rendered.length > queueLimit) {\n\t\t\tthis.#recordOverflow(event.id);\n\t\t} else {\n\t\t\tthis.#events.push(event);\n\t\t\tthis.#eventChars += rendered.length;\n\t\t}\n\t\tthis.#schedule(settings.coalesceWindowMs);\n\t}\n\n\t/** Any explicit user or tool activity breaks a consecutive monitor-only wake streak. */\n\tnoteActivity(): void {\n\t\tthis.#consecutiveWakes = 0;\n\t}\n\n\t/** Explicit rearm resumes intermediate line delivery after the session-global wake pause. */\n\trearm(id: string): void {\n\t\tthis.#wakeBudgetPaused = false;\n\t\tthis.#consecutiveWakes = 0;\n\t\tthis.#lastInjectionAt.delete(id);\n\t\tthis.#lastInjectedBatch.delete(id);\n\t}\n\n\tdispose(): void {\n\t\tif (this.#timer !== undefined) this.#scheduler.clearTimeout(this.#timer);\n\t\tthis.#timer = undefined;\n\t\tthis.#scheduledAt = undefined;\n\t\tthis.#events = [];\n\t\tthis.#eventChars = 0;\n\t\tthis.#overflow.clear();\n\t\tthis.#lastInjectedBatch.clear();\n\t}\n\n\t#recordOverflow(id: string): void {\n\t\tconst overflow = this.#overflow.get(id) ?? { id, count: 0 };\n\t\toverflow.count++;\n\t\tthis.#overflow.set(id, overflow);\n\t}\n\n\t#schedule(delayMs: number): void {\n\t\tconst due = this.#scheduler.now() + delayMs;\n\t\tif (this.#timer !== undefined && this.#scheduledAt !== undefined && this.#scheduledAt <= due) return;\n\t\tif (this.#timer !== undefined) this.#scheduler.clearTimeout(this.#timer);\n\t\tthis.#scheduledAt = due;\n\t\tthis.#timer = this.#scheduler.setTimeout(() => this.#flush(), delayMs);\n\t}\n\n\t#flush(): void {\n\t\tthis.#timer = undefined;\n\t\tthis.#scheduledAt = undefined;\n\t\tif (this.#wakeBudgetPaused) {\n\t\t\tthis.dispose();\n\t\t\treturn;\n\t\t}\n\t\tconst delivery = getTerminalNotificationDelivery(this.#deps, MONITOR_NOTIFICATION_CUSTOM_TYPE);\n\t\tif (!delivery) {\n\t\t\tthis.dispose();\n\t\t\treturn;\n\t\t}\n\n\t\tconst settings = resolveSettings(this.#deps.getSettings());\n\t\tconst now = this.#scheduler.now();\n\t\tconst pendingIds = new Set([...this.#events.map((event) => event.id), ...this.#overflow.keys()]);\n\t\tif (pendingIds.size === 0) return;\n\t\tconst ready = new Set(\n\t\t\t[...pendingIds].filter((id) => {\n\t\t\t\tconst last = this.#lastInjectionAt.get(id);\n\t\t\t\treturn last === undefined || now - last >= settings.rateLimitMs;\n\t\t\t}),\n\t\t);\n\t\tif (ready.size === 0) {\n\t\t\tthis.#scheduleNextRateLimit(pendingIds, now, settings);\n\t\t\treturn;\n\t\t}\n\n\t\tconst fingerprints = this.#batchFingerprints(ready);\n\t\tconst suppressedIds = new Set(\n\t\t\t[...fingerprints]\n\t\t\t\t.filter(([id, fingerprint]) => this.#lastInjectedBatch.get(id) === fingerprint)\n\t\t\t\t.map(([id]) => id),\n\t\t);\n\t\tconst injectedIds = new Set([...ready].filter((id) => !suppressedIds.has(id)));\n\t\tconst selected = this.#events.filter((event) => injectedIds.has(event.id));\n\t\tconst deferred = this.#events.filter((event) => !ready.has(event.id));\n\t\tif (injectedIds.size === 0) {\n\t\t\tthis.#discardSuppressed(deferred, now, settings);\n\t\t\treturn;\n\t\t}\n\t\tconst overflowCount = [...this.#overflow.values()]\n\t\t\t.filter((overflow) => injectedIds.has(overflow.id))\n\t\t\t.reduce((total, overflow) => total + overflow.count, 0);\n\t\tconst deliversSummary = selected.some((event) => event.type === \"summary\");\n\t\tconst reachesBudget = !deliversSummary && this.#consecutiveWakes + 1 >= settings.wakeBudget;\n\t\tconst pauseNotice = reachesBudget\n\t\t\t? \"Monitor paused after repeated updates. Completion still wakes this session; peek bash_output or re-arm only for intermediate events.\"\n\t\t\t: \"\";\n\t\tconst content = this.#buildMessage(selected, overflowCount, pauseNotice, settings.maxCharsPerInjection);\n\n\t\tdelivery.send(content);\n\t\tfor (const id of injectedIds) {\n\t\t\tthis.#lastInjectionAt.set(id, now);\n\t\t\tthis.#overflow.delete(id);\n\t\t\tconst fingerprint = fingerprints.get(id);\n\t\t\tif (fingerprint === undefined) this.#lastInjectedBatch.delete(id);\n\t\t\telse this.#lastInjectedBatch.set(id, fingerprint);\n\t\t}\n\t\tthis.#events = deferred;\n\t\tthis.#eventChars = deferred.reduce(\n\t\t\t(total, event) => total + `Monitor event(${event.description}): ${eventBody(event)}`.length,\n\t\t\t0,\n\t\t);\n\t\tthis.#consecutiveWakes = deliversSummary ? 0 : this.#consecutiveWakes + 1;\n\n\t\tif (reachesBudget) {\n\t\t\tthis.#wakeBudgetPaused = true;\n\t\t\tthis.#deps.pauseMonitors();\n\t\t\tthis.#events = [];\n\t\t\tthis.#eventChars = 0;\n\t\t\tthis.#overflow.clear();\n\t\t\treturn;\n\t\t}\n\t\tconst remainingIds = new Set([...this.#events.map((event) => event.id), ...this.#overflow.keys()]);\n\t\tif (remainingIds.size > 0) this.#scheduleNextRateLimit(remainingIds, now, settings);\n\t}\n\n\t#scheduleNextRateLimit(ids: ReadonlySet<string>, now: number, settings: MonitorDeliverySettings): void {\n\t\tconst nextAt = Math.min(...[...ids].map((id) => (this.#lastInjectionAt.get(id) ?? now) + settings.rateLimitMs));\n\t\tthis.#schedule(Math.max(1, nextAt - now));\n\t}\n\n\t/** A ready monitor whose line-only batch matches its previous injection carries no new information. */\n\t#batchFingerprints(ids: ReadonlySet<string>): Map<string, string> {\n\t\tconst fingerprints = new Map<string, string>();\n\t\tfor (const id of ids) {\n\t\t\tif ((this.#overflow.get(id)?.count ?? 0) > 0) continue;\n\t\t\tconst batch = this.#events.filter((event) => event.id === id);\n\t\t\tif (batch.length === 0 || batch.some((event) => event.type !== \"line\")) continue;\n\t\t\tfingerprints.set(id, batch.map(eventBody).join(\"\\n\"));\n\t\t}\n\t\treturn fingerprints;\n\t}\n\n\t#discardSuppressed(deferred: MonitorEvent[], now: number, settings: MonitorDeliverySettings): void {\n\t\tthis.#events = deferred;\n\t\tthis.#eventChars = deferred.reduce(\n\t\t\t(total, event) => total + `Monitor event(${event.description}): ${eventBody(event)}`.length,\n\t\t\t0,\n\t\t);\n\t\tconst remainingIds = new Set([...deferred.map((event) => event.id), ...this.#overflow.keys()]);\n\t\tif (remainingIds.size > 0) this.#scheduleNextRateLimit(remainingIds, now, settings);\n\t}\n\n\t#buildMessage(\n\t\tevents: readonly MonitorEvent[],\n\t\toverflowCount: number,\n\t\tpauseNotice: string,\n\t\tmaxChars: number,\n\t): string {\n\t\tconst overflowNotice =\n\t\t\toverflowCount > 0\n\t\t\t\t? `[${overflowCount} additional event lines omitted; peek bash_output for full history.]`\n\t\t\t\t: \"\";\n\t\tconst suffix = [overflowNotice, pauseNotice].filter(Boolean).join(\"\\n\");\n\t\tconst fixedChars = SYSTEM_REMINDER_OPEN.length + SYSTEM_REMINDER_CLOSE.length + (suffix ? suffix.length + 1 : 0);\n\t\tconst body = clip(formatEvents(events), Math.max(0, maxChars - fixedChars));\n\t\treturn `${SYSTEM_REMINDER_OPEN}${body}${body && suffix ? \"\\n\" : \"\"}${suffix}${SYSTEM_REMINDER_CLOSE}`;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"monitor-notify.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/terminal/monitor-notify.ts"],"names":[],"mappings":"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","sourcesContent":["import type { MonitorEvent } from \"./monitor-registry.ts\";\nimport { getTerminalNotificationDelivery, type TerminalNotifierDeps } from \"./notify.ts\";\nimport { sanitizeTerminalOutput } from \"./output-format.ts\";\nimport type { MonitorDeliverySettings } from \"./settings.ts\";\n\nconst SYSTEM_REMINDER_OPEN = \"<system-reminder>\";\nconst SYSTEM_REMINDER_CLOSE = \"</system-reminder>\";\nconst QUEUE_OVERHEAD_CHARS = 512;\nconst MONITOR_NOTIFICATION_CUSTOM_TYPE = \"senpi-monitor:notification\";\nconst WAKE_STREAK_QUIET_GAP_MULTIPLIER = 2;\n\nexport interface MonitorNotificationScheduler {\n\tnow(): number;\n\tsetTimeout(callback: () => void, delayMs: number): unknown;\n\tclearTimeout(timer: unknown): void;\n}\n\nconst systemScheduler: MonitorNotificationScheduler = {\n\tnow: () => Date.now(),\n\tsetTimeout: (callback, delayMs) => setTimeout(callback, delayMs),\n\tclearTimeout: (timer) => clearTimeout(timer as ReturnType<typeof setTimeout>),\n};\n\nexport interface MonitorNotifierDeps extends TerminalNotifierDeps {\n\treadonly getSettings: () => MonitorDeliverySettings;\n\t/** Pause live monitors when their shared wake budget is exhausted. */\n\treadonly pauseMonitors: () => readonly string[];\n\treadonly scheduler?: MonitorNotificationScheduler;\n}\n\ninterface Overflow {\n\treadonly id: string;\n\tcount: number;\n}\n\nfunction boundedPositiveInt(value: number, fallback: number, minimum: number, maximum: number): number {\n\tif (!Number.isFinite(value)) return fallback;\n\treturn Math.min(Math.max(Math.trunc(value), minimum), maximum);\n}\n\nfunction resolveSettings(settings: MonitorDeliverySettings): MonitorDeliverySettings {\n\treturn {\n\t\tcoalesceWindowMs: boundedPositiveInt(settings.coalesceWindowMs, 2000, 1, 60_000),\n\t\trateLimitMs: boundedPositiveInt(settings.rateLimitMs, 5000, 1, 3_600_000),\n\t\tmaxLinesPerInjection: boundedPositiveInt(settings.maxLinesPerInjection, 50, 1, 200),\n\t\tmaxCharsPerInjection: boundedPositiveInt(settings.maxCharsPerInjection, 4096, 512, 16_384),\n\t\twakeBudget: boundedPositiveInt(settings.wakeBudget, 5, 1, 100),\n\t};\n}\n\nfunction eventBody(event: MonitorEvent): string {\n\tconst value = event.type === \"line\" ? event.line : event.summary;\n\treturn sanitizeTerminalOutput(value)\n\t\t.replace(/[\\r\\n]+/g, \" \")\n\t\t.trimEnd();\n}\n\nfunction clip(text: string, maxChars: number): string {\n\tif (text.length <= maxChars) return text;\n\tif (maxChars <= 3) return text.slice(0, maxChars);\n\treturn `${text.slice(0, maxChars - 3)}...`;\n}\n\nfunction formatEvents(events: readonly MonitorEvent[]): string {\n\tconst groups = new Map<string, { description: string; bodies: string[] }>();\n\tfor (const event of events) {\n\t\tconst group = groups.get(event.id) ?? { description: event.description, bodies: [] };\n\t\tgroup.bodies.push(eventBody(event));\n\t\tgroups.set(event.id, group);\n\t}\n\treturn [...groups.values()]\n\t\t.map((group) => `Monitor event(${group.description}): ${group.bodies.join(\"\\n\")}`)\n\t\t.join(\"\\n\");\n}\n\n/**\n * Session-scoped monitor delivery queue. It preserves the terminal runtime as the authoritative,\n * bounded event history while retaining only one capped coalescing batch for chat injection.\n */\nexport class MonitorNotifier {\n\treadonly #deps: MonitorNotifierDeps;\n\treadonly #scheduler: MonitorNotificationScheduler;\n\t#events: MonitorEvent[] = [];\n\t#eventChars = 0;\n\t#overflow = new Map<string, Overflow>();\n\t#lastInjectionAt = new Map<string, number>();\n\t#lastInjectedBatch = new Map<string, string>();\n\t#timer: unknown;\n\t#scheduledAt: number | undefined;\n\t#consecutiveWakes = 0;\n\t#lastWakeAt: number | undefined;\n\t#wakeBudgetPaused = false;\n\n\tconstructor(deps: MonitorNotifierDeps) {\n\t\tthis.#deps = deps;\n\t\tthis.#scheduler = deps.scheduler ?? systemScheduler;\n\t}\n\n\tnotifyEvent(event: MonitorEvent): void {\n\t\tif (this.#wakeBudgetPaused) {\n\t\t\tif (event.type === \"line\") return;\n\t\t\tthis.#wakeBudgetPaused = false;\n\t\t\tthis.#consecutiveWakes = 0;\n\t\t\tthis.#lastInjectionAt.delete(event.id);\n\t\t}\n\t\tif (!getTerminalNotificationDelivery(this.#deps, MONITOR_NOTIFICATION_CUSTOM_TYPE)) return;\n\t\tconst settings = resolveSettings(this.#deps.getSettings());\n\t\tconst rendered = `Monitor event(${event.description}): ${eventBody(event)}`;\n\t\tconst queueLimit = Math.max(1, settings.maxCharsPerInjection - QUEUE_OVERHEAD_CHARS);\n\t\tif (this.#events.length >= settings.maxLinesPerInjection || this.#eventChars + rendered.length > queueLimit) {\n\t\t\tthis.#recordOverflow(event.id);\n\t\t} else {\n\t\t\tthis.#events.push(event);\n\t\t\tthis.#eventChars += rendered.length;\n\t\t}\n\t\tthis.#schedule(settings.coalesceWindowMs);\n\t}\n\n\t/** Any explicit user or tool activity breaks a consecutive monitor-only wake streak. */\n\tnoteActivity(): void {\n\t\tthis.#consecutiveWakes = 0;\n\t}\n\n\t/** Explicit rearm resumes intermediate line delivery after the session-global wake pause. */\n\trearm(id: string): void {\n\t\tthis.#wakeBudgetPaused = false;\n\t\tthis.#consecutiveWakes = 0;\n\t\tthis.#lastInjectionAt.delete(id);\n\t\tthis.#lastInjectedBatch.delete(id);\n\t}\n\n\tdispose(): void {\n\t\tif (this.#timer !== undefined) this.#scheduler.clearTimeout(this.#timer);\n\t\tthis.#timer = undefined;\n\t\tthis.#scheduledAt = undefined;\n\t\tthis.#events = [];\n\t\tthis.#eventChars = 0;\n\t\tthis.#overflow.clear();\n\t\tthis.#lastInjectedBatch.clear();\n\t}\n\n\t#recordOverflow(id: string): void {\n\t\tconst overflow = this.#overflow.get(id) ?? { id, count: 0 };\n\t\toverflow.count++;\n\t\tthis.#overflow.set(id, overflow);\n\t}\n\n\t#schedule(delayMs: number): void {\n\t\tconst due = this.#scheduler.now() + delayMs;\n\t\tif (this.#timer !== undefined && this.#scheduledAt !== undefined && this.#scheduledAt <= due) return;\n\t\tif (this.#timer !== undefined) this.#scheduler.clearTimeout(this.#timer);\n\t\tthis.#scheduledAt = due;\n\t\tthis.#timer = this.#scheduler.setTimeout(() => this.#flush(), delayMs);\n\t}\n\n\t#flush(): void {\n\t\tthis.#timer = undefined;\n\t\tthis.#scheduledAt = undefined;\n\t\tif (this.#wakeBudgetPaused) {\n\t\t\tthis.dispose();\n\t\t\treturn;\n\t\t}\n\t\tconst delivery = getTerminalNotificationDelivery(this.#deps, MONITOR_NOTIFICATION_CUSTOM_TYPE);\n\t\tif (!delivery) {\n\t\t\tthis.dispose();\n\t\t\treturn;\n\t\t}\n\n\t\tconst settings = resolveSettings(this.#deps.getSettings());\n\t\tconst now = this.#scheduler.now();\n\t\tconst pendingIds = new Set([...this.#events.map((event) => event.id), ...this.#overflow.keys()]);\n\t\tif (pendingIds.size === 0) return;\n\t\tconst ready = new Set(\n\t\t\t[...pendingIds].filter((id) => {\n\t\t\t\tconst last = this.#lastInjectionAt.get(id);\n\t\t\t\treturn last === undefined || now - last >= settings.rateLimitMs;\n\t\t\t}),\n\t\t);\n\t\tif (ready.size === 0) {\n\t\t\tthis.#scheduleNextRateLimit(pendingIds, now, settings);\n\t\t\treturn;\n\t\t}\n\n\t\tconst fingerprints = this.#batchFingerprints(ready);\n\t\tconst suppressedIds = new Set(\n\t\t\t[...fingerprints]\n\t\t\t\t.filter(([id, fingerprint]) => this.#lastInjectedBatch.get(id) === fingerprint)\n\t\t\t\t.map(([id]) => id),\n\t\t);\n\t\tconst injectedIds = new Set([...ready].filter((id) => !suppressedIds.has(id)));\n\t\tconst selected = this.#events.filter((event) => injectedIds.has(event.id));\n\t\tconst deferred = this.#events.filter((event) => !ready.has(event.id));\n\t\tif (injectedIds.size === 0) {\n\t\t\tthis.#discardSuppressed(deferred, now, settings);\n\t\t\treturn;\n\t\t}\n\t\tconst overflowCount = [...this.#overflow.values()]\n\t\t\t.filter((overflow) => injectedIds.has(overflow.id))\n\t\t\t.reduce((total, overflow) => total + overflow.count, 0);\n\t\tif (\n\t\t\tthis.#lastWakeAt !== undefined &&\n\t\t\tnow - this.#lastWakeAt > settings.rateLimitMs * WAKE_STREAK_QUIET_GAP_MULTIPLIER\n\t\t) {\n\t\t\tthis.#consecutiveWakes = 0;\n\t\t}\n\t\tconst deliversSummary = selected.some((event) => event.type === \"summary\");\n\t\tconst reachesBudget = !deliversSummary && this.#consecutiveWakes + 1 >= settings.wakeBudget;\n\t\tconst pauseNotice = reachesBudget\n\t\t\t? \"Monitor paused after repeated updates. Completion still wakes this session; peek bash_output or re-arm only for intermediate events.\"\n\t\t\t: \"\";\n\t\tconst content = this.#buildMessage(selected, overflowCount, pauseNotice, settings.maxCharsPerInjection);\n\n\t\tdelivery.send(content, reachesBudget ? { forceWake: true } : undefined);\n\t\tthis.#lastWakeAt = now;\n\t\tfor (const id of injectedIds) {\n\t\t\tthis.#lastInjectionAt.set(id, now);\n\t\t\tthis.#overflow.delete(id);\n\t\t\tconst fingerprint = fingerprints.get(id);\n\t\t\tif (fingerprint === undefined) this.#lastInjectedBatch.delete(id);\n\t\t\telse this.#lastInjectedBatch.set(id, fingerprint);\n\t\t}\n\t\tthis.#events = deferred;\n\t\tthis.#eventChars = deferred.reduce(\n\t\t\t(total, event) => total + `Monitor event(${event.description}): ${eventBody(event)}`.length,\n\t\t\t0,\n\t\t);\n\t\tthis.#consecutiveWakes = deliversSummary ? 0 : this.#consecutiveWakes + 1;\n\n\t\tif (reachesBudget) {\n\t\t\tthis.#wakeBudgetPaused = true;\n\t\t\tthis.#deps.pauseMonitors();\n\t\t\tthis.#events = [];\n\t\t\tthis.#eventChars = 0;\n\t\t\tthis.#overflow.clear();\n\t\t\treturn;\n\t\t}\n\t\tconst remainingIds = new Set([...this.#events.map((event) => event.id), ...this.#overflow.keys()]);\n\t\tif (remainingIds.size > 0) this.#scheduleNextRateLimit(remainingIds, now, settings);\n\t}\n\n\t#scheduleNextRateLimit(ids: ReadonlySet<string>, now: number, settings: MonitorDeliverySettings): void {\n\t\tconst nextAt = Math.min(...[...ids].map((id) => (this.#lastInjectionAt.get(id) ?? now) + settings.rateLimitMs));\n\t\tthis.#schedule(Math.max(1, nextAt - now));\n\t}\n\n\t/** A ready monitor whose line-only batch matches its previous injection carries no new information. */\n\t#batchFingerprints(ids: ReadonlySet<string>): Map<string, string> {\n\t\tconst fingerprints = new Map<string, string>();\n\t\tfor (const id of ids) {\n\t\t\tif ((this.#overflow.get(id)?.count ?? 0) > 0) continue;\n\t\t\tconst batch = this.#events.filter((event) => event.id === id);\n\t\t\tif (batch.length === 0 || batch.some((event) => event.type !== \"line\")) continue;\n\t\t\tfingerprints.set(id, batch.map(eventBody).join(\"\\n\"));\n\t\t}\n\t\treturn fingerprints;\n\t}\n\n\t#discardSuppressed(deferred: MonitorEvent[], now: number, settings: MonitorDeliverySettings): void {\n\t\tthis.#events = deferred;\n\t\tthis.#eventChars = deferred.reduce(\n\t\t\t(total, event) => total + `Monitor event(${event.description}): ${eventBody(event)}`.length,\n\t\t\t0,\n\t\t);\n\t\tconst remainingIds = new Set([...deferred.map((event) => event.id), ...this.#overflow.keys()]);\n\t\tif (remainingIds.size > 0) this.#scheduleNextRateLimit(remainingIds, now, settings);\n\t}\n\n\t#buildMessage(\n\t\tevents: readonly MonitorEvent[],\n\t\toverflowCount: number,\n\t\tpauseNotice: string,\n\t\tmaxChars: number,\n\t): string {\n\t\tconst overflowNotice =\n\t\t\toverflowCount > 0\n\t\t\t\t? `[${overflowCount} additional event lines omitted; peek bash_output for full history.]`\n\t\t\t\t: \"\";\n\t\tconst suffix = [overflowNotice, pauseNotice].filter(Boolean).join(\"\\n\");\n\t\tconst fixedChars = SYSTEM_REMINDER_OPEN.length + SYSTEM_REMINDER_CLOSE.length + (suffix ? suffix.length + 1 : 0);\n\t\tconst body = clip(formatEvents(events), Math.max(0, maxChars - fixedChars));\n\t\treturn `${SYSTEM_REMINDER_OPEN}${body}${body && suffix ? \"\\n\" : \"\"}${suffix}${SYSTEM_REMINDER_CLOSE}`;\n\t}\n}\n"]}
|
|
@@ -20,7 +20,9 @@ export interface TerminalNotifierDeps {
|
|
|
20
20
|
/** Max chars of sanitized final output embedded in a completion notification. */
|
|
21
21
|
export declare const NOTICE_TAIL_MAX_CHARS = 2000;
|
|
22
22
|
export interface TerminalNotificationDelivery {
|
|
23
|
-
readonly send: (content: string
|
|
23
|
+
readonly send: (content: string, options?: {
|
|
24
|
+
readonly forceWake?: boolean;
|
|
25
|
+
}) => void;
|
|
24
26
|
}
|
|
25
27
|
/** Shared terminal-notification guard and notify-mode mapping. */
|
|
26
28
|
export declare function getTerminalNotificationDelivery(deps: TerminalNotifierDeps, customType?: string): TerminalNotificationDelivery | undefined;
|