@oh-my-pi/pi-coding-agent 15.12.3 → 15.13.0
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 +347 -7
- package/dist/cli.js +1615 -1231
- package/dist/types/async/job-manager.d.ts +15 -0
- package/dist/types/autolearn/controller.d.ts +25 -0
- package/dist/types/autolearn/managed-skills.d.ts +45 -0
- package/dist/types/autoresearch/state.d.ts +1 -1
- package/dist/types/autoresearch/tools/init-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/log-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/run-experiment.d.ts +1 -1
- package/dist/types/autoresearch/tools/update-notes.d.ts +1 -1
- package/dist/types/autoresearch/types.d.ts +1 -1
- package/dist/types/cli/args.d.ts +19 -2
- package/dist/types/cli/models-cli.d.ts +49 -0
- package/dist/types/cli/session-picker.d.ts +1 -1
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/cli/setup-model-picker.d.ts +14 -0
- package/dist/types/collab/protocol.d.ts +1 -1
- package/dist/types/commands/launch.d.ts +0 -3
- package/dist/types/commands/models.d.ts +33 -0
- package/dist/types/commands/say.d.ts +24 -0
- package/dist/types/commands/token.d.ts +25 -0
- package/dist/types/commit/agentic/tools/analyze-file.d.ts +1 -1
- package/dist/types/commit/agentic/tools/git-file-diff.d.ts +1 -1
- package/dist/types/commit/agentic/tools/git-hunk.d.ts +1 -1
- package/dist/types/commit/agentic/tools/git-overview.d.ts +1 -1
- package/dist/types/commit/agentic/tools/propose-changelog.d.ts +1 -1
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +1 -1
- package/dist/types/commit/agentic/tools/recent-commits.d.ts +1 -1
- package/dist/types/commit/agentic/tools/schemas.d.ts +1 -1
- package/dist/types/commit/agentic/tools/split-commit.d.ts +1 -1
- package/dist/types/commit/changelog/generate.d.ts +1 -1
- package/dist/types/commit/shared-llm.d.ts +1 -1
- package/dist/types/config/keybindings.d.ts +3 -3
- package/dist/types/config/model-registry.d.ts +17 -0
- package/dist/types/config/models-config-schema.d.ts +13 -1
- package/dist/types/config/models-config.d.ts +8 -2
- package/dist/types/config/settings-schema.d.ts +281 -58
- package/dist/types/edit/hashline/params.d.ts +1 -1
- package/dist/types/edit/modes/apply-patch.d.ts +1 -1
- package/dist/types/edit/modes/patch.d.ts +1 -1
- package/dist/types/edit/modes/replace.d.ts +1 -1
- package/dist/types/export/html/index.d.ts +2 -1
- package/dist/types/extensibility/custom-commands/types.d.ts +2 -2
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/model-api.d.ts +17 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +49 -3
- package/dist/types/extensibility/hooks/index.d.ts +2 -1
- package/dist/types/extensibility/hooks/types.d.ts +2 -2
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +9 -0
- package/dist/types/extensibility/plugins/loader.d.ts +11 -0
- package/dist/types/extensibility/shared-events.d.ts +1 -1
- package/dist/types/extensibility/skills.d.ts +10 -0
- package/dist/types/goals/guided-setup.d.ts +18 -0
- package/dist/types/goals/state.d.ts +1 -1
- package/dist/types/goals/tools/goal-tool.d.ts +1 -1
- package/dist/types/hindsight/transcript.d.ts +1 -1
- package/dist/types/index.d.ts +5 -0
- package/dist/types/internal-urls/local-protocol.d.ts +4 -2
- package/dist/types/lsp/types.d.ts +1 -1
- package/dist/types/main.d.ts +4 -3
- package/dist/types/mcp/manager.d.ts +8 -0
- package/dist/types/mcp/startup-events.d.ts +11 -0
- package/dist/types/memories/index.d.ts +7 -0
- package/dist/types/memory-backend/local-backend.d.ts +4 -3
- package/dist/types/mnemopi/config.d.ts +28 -0
- package/dist/types/modes/acp/acp-agent.d.ts +1 -2
- package/dist/types/modes/components/agent-hub.d.ts +6 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -2
- package/dist/types/modes/components/compaction-summary-message.d.ts +15 -1
- package/dist/types/modes/components/custom-editor.d.ts +39 -1
- package/dist/types/modes/components/custom-editor.test.d.ts +1 -0
- package/dist/types/modes/components/index.d.ts +1 -0
- package/dist/types/modes/components/logout-account-selector.d.ts +8 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/status-line/component.d.ts +9 -5
- package/dist/types/modes/components/status-line/types.d.ts +2 -1
- package/dist/types/modes/components/tool-execution.d.ts +26 -16
- package/dist/types/modes/components/transcript-container.d.ts +23 -2
- package/dist/types/modes/components/tree-selector.d.ts +1 -1
- package/dist/types/modes/components/usage-row.d.ts +3 -0
- package/dist/types/modes/controllers/command-controller.d.ts +2 -2
- package/dist/types/modes/controllers/event-controller.d.ts +0 -17
- package/dist/types/modes/controllers/input-controller.d.ts +14 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
- package/dist/types/modes/gradient-highlight.d.ts +9 -4
- package/dist/types/modes/image-references.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +27 -6
- package/dist/types/modes/magic-keywords.d.ts +13 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +35 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +9 -1
- package/dist/types/modes/runtime-init.d.ts +4 -0
- package/dist/types/modes/theme/theme.d.ts +13 -2
- package/dist/types/modes/types.d.ts +8 -7
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
- package/dist/types/registry/agent-registry.d.ts +17 -0
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +28 -35
- package/dist/types/session/agent-storage.d.ts +2 -1
- package/dist/types/session/indexed-session-storage.d.ts +3 -3
- package/dist/types/session/messages.d.ts +8 -10
- package/dist/types/session/session-context.d.ts +39 -0
- package/dist/types/session/session-entries.d.ts +159 -0
- package/dist/types/session/session-listing.d.ts +69 -0
- package/dist/types/session/session-loader.d.ts +16 -0
- package/dist/types/session/session-manager.d.ts +85 -462
- package/dist/types/session/session-migrations.d.ts +12 -0
- package/dist/types/session/session-paths.d.ts +25 -0
- package/dist/types/session/session-persistence.d.ts +8 -0
- package/dist/types/session/session-storage.d.ts +11 -7
- package/dist/types/session/snapcompact-inline.d.ts +12 -1
- package/dist/types/session/snapcompact-savings-journal.d.ts +46 -0
- package/dist/types/session/tool-choice-queue.d.ts +6 -6
- package/dist/types/slash-commands/helpers/logout.d.ts +15 -0
- package/dist/types/stt/asr-client.d.ts +90 -0
- package/dist/types/stt/asr-protocol.d.ts +97 -0
- package/dist/types/stt/asr-worker.d.ts +2 -0
- package/dist/types/stt/downloader.d.ts +38 -0
- package/dist/types/stt/endpointer.d.ts +59 -0
- package/dist/types/stt/index.d.ts +5 -1
- package/dist/types/stt/models.d.ts +120 -0
- package/dist/types/stt/recorder.d.ts +17 -0
- package/dist/types/stt/stt-controller.d.ts +6 -0
- package/dist/types/stt/transcriber.d.ts +5 -7
- package/dist/types/stt/wav.d.ts +29 -0
- package/dist/types/system-prompt.d.ts +4 -0
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/task/index.d.ts +9 -1
- package/dist/types/task/types.d.ts +37 -1
- package/dist/types/tools/ask.d.ts +1 -1
- package/dist/types/tools/ast-edit.d.ts +1 -1
- package/dist/types/tools/ast-grep.d.ts +1 -1
- package/dist/types/tools/bash.d.ts +3 -3
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +202 -0
- package/dist/types/tools/browser/cmux/rpc.d.ts +70 -0
- package/dist/types/tools/browser/cmux/socket-client.d.ts +19 -0
- package/dist/types/tools/browser/registry.d.ts +16 -3
- package/dist/types/tools/browser/render.d.ts +2 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +2 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +16 -4
- package/dist/types/tools/browser.d.ts +3 -1
- package/dist/types/tools/checkpoint.d.ts +1 -1
- package/dist/types/tools/debug.d.ts +1 -1
- package/dist/types/tools/eval-render.d.ts +1 -1
- package/dist/types/tools/eval.d.ts +1 -1
- package/dist/types/tools/find.d.ts +1 -1
- package/dist/types/tools/gh.d.ts +1 -1
- package/dist/types/tools/image-gen.d.ts +1 -1
- package/dist/types/tools/index.d.ts +14 -2
- package/dist/types/tools/inspect-image.d.ts +1 -1
- package/dist/types/tools/irc.d.ts +2 -1
- package/dist/types/tools/job.d.ts +1 -1
- package/dist/types/tools/learn.d.ts +51 -0
- package/dist/types/tools/manage-skill.d.ts +40 -0
- package/dist/types/tools/memory-edit.d.ts +1 -1
- package/dist/types/tools/memory-recall.d.ts +1 -1
- package/dist/types/tools/memory-reflect.d.ts +1 -1
- package/dist/types/tools/memory-retain.d.ts +1 -1
- package/dist/types/tools/plan-mode-guard.d.ts +10 -0
- package/dist/types/tools/read.d.ts +1 -1
- package/dist/types/tools/render-mermaid.d.ts +1 -1
- package/dist/types/tools/renderers.d.ts +7 -11
- package/dist/types/tools/resolve.d.ts +1 -1
- package/dist/types/tools/review.d.ts +1 -1
- package/dist/types/tools/search-tool-bm25.d.ts +1 -1
- package/dist/types/tools/search.d.ts +1 -1
- package/dist/types/tools/ssh.d.ts +2 -2
- package/dist/types/tools/todo.d.ts +2 -2
- package/dist/types/tools/tts.d.ts +26 -1
- package/dist/types/tools/write.d.ts +2 -2
- package/dist/types/tts/downloader.d.ts +20 -0
- package/dist/types/tts/index.d.ts +8 -0
- package/dist/types/tts/models.d.ts +82 -0
- package/dist/types/tts/player.d.ts +32 -0
- package/dist/types/tts/runtime.d.ts +6 -0
- package/dist/types/tts/streaming-player.d.ts +41 -0
- package/dist/types/tts/tts-client.d.ts +93 -0
- package/dist/types/tts/tts-protocol.d.ts +95 -0
- package/dist/types/tts/tts-worker.d.ts +2 -0
- package/dist/types/tts/vocalizer.d.ts +41 -0
- package/dist/types/tts/wav.d.ts +8 -0
- package/dist/types/utils/clipboard.d.ts +4 -3
- package/dist/types/utils/image-loading.d.ts +18 -1
- package/dist/types/utils/thinking-display.d.ts +17 -0
- package/dist/types/utils/tool-choice.d.ts +8 -0
- package/dist/types/utils/tools-manager.d.ts +2 -1
- package/dist/types/utils/tools-manager.test.d.ts +1 -0
- package/dist/types/web/scrapers/github.d.ts +1 -1
- package/dist/types/web/search/index.d.ts +1 -1
- package/package.json +17 -16
- package/src/async/job-manager.ts +49 -0
- package/src/autolearn/controller.ts +139 -0
- package/src/autolearn/managed-skills.ts +257 -0
- package/src/autoresearch/state.ts +1 -1
- package/src/autoresearch/storage.ts +2 -1
- package/src/autoresearch/tools/init-experiment.ts +1 -1
- package/src/autoresearch/tools/log-experiment.ts +1 -1
- package/src/autoresearch/tools/run-experiment.ts +1 -1
- package/src/autoresearch/tools/update-notes.ts +1 -1
- package/src/autoresearch/types.ts +1 -1
- package/src/cli/args.ts +56 -10
- package/src/cli/auth-gateway-cli.ts +1 -1
- package/src/cli/bench-cli.ts +1 -1
- package/src/cli/dry-balance-cli.ts +1 -1
- package/src/cli/models-cli.ts +427 -0
- package/src/cli/session-picker.ts +2 -1
- package/src/cli/setup-cli.ts +148 -47
- package/src/cli/setup-model-picker.ts +43 -0
- package/src/cli-commands.ts +3 -0
- package/src/cli.ts +45 -13
- package/src/collab/host.ts +10 -13
- package/src/collab/protocol.ts +1 -1
- package/src/commands/launch.ts +0 -3
- package/src/commands/models.ts +61 -0
- package/src/commands/say.ts +102 -0
- package/src/commands/setup.ts +1 -1
- package/src/commands/token.ts +89 -0
- package/src/commit/agentic/tools/analyze-file.ts +4 -1
- package/src/commit/agentic/tools/git-file-diff.ts +1 -1
- package/src/commit/agentic/tools/git-hunk.ts +1 -1
- package/src/commit/agentic/tools/git-overview.ts +1 -1
- package/src/commit/agentic/tools/propose-changelog.ts +1 -1
- package/src/commit/agentic/tools/propose-commit.ts +1 -1
- package/src/commit/agentic/tools/recent-commits.ts +1 -1
- package/src/commit/agentic/tools/schemas.ts +1 -1
- package/src/commit/agentic/tools/split-commit.ts +1 -1
- package/src/commit/analysis/summary.ts +1 -1
- package/src/commit/changelog/generate.ts +1 -1
- package/src/commit/shared-llm.ts +1 -1
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-discovery.ts +11 -5
- package/src/config/model-registry.ts +79 -21
- package/src/config/model-resolver.ts +2 -2
- package/src/config/models-config-schema.ts +5 -2
- package/src/config/models-config.ts +2 -1
- package/src/config/settings-schema.ts +266 -32
- package/src/config/settings.ts +10 -0
- package/src/discovery/builtin.ts +23 -1
- package/src/discovery/claude-plugins.ts +44 -5
- package/src/discovery/helpers.ts +41 -1
- package/src/edit/hashline/params.ts +1 -1
- package/src/edit/modes/apply-patch.ts +1 -1
- package/src/edit/modes/patch.ts +1 -1
- package/src/edit/modes/replace.ts +1 -1
- package/src/eval/__tests__/budget-bridge.test.ts +1 -1
- package/src/eval/agent-bridge.ts +1 -1
- package/src/eval/completion-bridge.ts +1 -1
- package/src/eval/js/shared/prelude.txt +69 -17
- package/src/export/html/index.ts +3 -6
- package/src/export/html/template.js +24 -2
- package/src/export/html/tool-views.generated.js +2 -2
- package/src/extensibility/custom-commands/loader.ts +1 -1
- package/src/extensibility/custom-commands/types.ts +2 -2
- package/src/extensibility/custom-tools/loader.ts +1 -1
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/extensions/loader.ts +2 -2
- package/src/extensibility/extensions/model-api.ts +41 -0
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +54 -3
- package/src/extensibility/extensions/wrapper.ts +41 -5
- package/src/extensibility/hooks/index.ts +2 -1
- package/src/extensibility/hooks/loader.ts +1 -1
- package/src/extensibility/hooks/types.ts +2 -2
- package/src/extensibility/plugins/legacy-pi-compat.ts +43 -13
- package/src/extensibility/plugins/loader.ts +30 -19
- package/src/extensibility/plugins/manager.ts +221 -90
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skills.ts +101 -5
- package/src/goals/guided-setup.ts +133 -0
- package/src/goals/state.ts +1 -1
- package/src/goals/tools/goal-tool.ts +1 -1
- package/src/hindsight/transcript.ts +1 -1
- package/src/index.ts +5 -0
- package/src/internal-urls/docs-index.generated.ts +13 -10
- package/src/internal-urls/history-protocol.ts +1 -1
- package/src/internal-urls/local-protocol.ts +29 -7
- package/src/lsp/types.ts +1 -1
- package/src/main.ts +27 -32
- package/src/mcp/config-writer.ts +7 -3
- package/src/mcp/manager.ts +11 -0
- package/src/mcp/startup-events.ts +21 -0
- package/src/mcp/transports/stdio.ts +2 -1
- package/src/memories/index.ts +149 -12
- package/src/memories/storage.ts +2 -1
- package/src/memory-backend/local-backend.ts +11 -5
- package/src/mnemopi/backend.ts +1 -0
- package/src/mnemopi/config.ts +112 -12
- package/src/modes/acp/acp-agent.ts +8 -53
- package/src/modes/acp/acp-event-mapper.ts +5 -1
- package/src/modes/components/agent-hub.ts +51 -5
- package/src/modes/components/assistant-message.ts +12 -44
- package/src/modes/components/compaction-summary-message.ts +125 -26
- package/src/modes/components/custom-editor.test.ts +96 -0
- package/src/modes/components/custom-editor.ts +164 -8
- package/src/modes/components/index.ts +1 -0
- package/src/modes/components/logout-account-selector.ts +130 -0
- package/src/modes/components/mcp-add-wizard.ts +1 -1
- package/src/modes/components/model-selector.ts +2 -2
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/settings-defs.ts +7 -0
- package/src/modes/components/status-line/component.ts +54 -157
- package/src/modes/components/status-line/segments.ts +1 -1
- package/src/modes/components/status-line/types.ts +2 -1
- package/src/modes/components/tool-execution.ts +82 -43
- package/src/modes/components/transcript-container.ts +70 -1
- package/src/modes/components/tree-selector.ts +1 -1
- package/src/modes/components/usage-row.ts +18 -0
- package/src/modes/components/user-message.ts +4 -2
- package/src/modes/controllers/command-controller.ts +14 -16
- package/src/modes/controllers/event-controller.ts +101 -73
- package/src/modes/controllers/extension-ui-controller.ts +6 -0
- package/src/modes/controllers/input-controller.ts +311 -57
- package/src/modes/controllers/mcp-command-controller.ts +44 -3
- package/src/modes/controllers/selector-controller.ts +68 -12
- package/src/modes/controllers/streaming-reveal.ts +4 -3
- package/src/modes/gradient-highlight.ts +21 -9
- package/src/modes/image-references.ts +20 -0
- package/src/modes/interactive-mode.ts +288 -48
- package/src/modes/magic-keywords.ts +27 -5
- package/src/modes/rpc/rpc-mode.ts +146 -14
- package/src/modes/rpc/rpc-subagents.ts +2 -2
- package/src/modes/rpc/rpc-types.ts +8 -2
- package/src/modes/runtime-init.ts +28 -3
- package/src/modes/theme/theme.ts +99 -51
- package/src/modes/types.ts +6 -7
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/modes/utils/ui-helpers.ts +36 -7
- package/src/priority.json +5 -1
- package/src/prompts/agents/task.md +1 -0
- package/src/prompts/goals/guided-goal-interview.md +8 -0
- package/src/prompts/goals/guided-goal-system.md +12 -0
- package/src/prompts/memories/read-path.md +6 -0
- package/src/prompts/system/autolearn-guidance-learn.md +1 -0
- package/src/prompts/system/autolearn-guidance.md +7 -0
- package/src/prompts/system/autolearn-nudge.md +3 -0
- package/src/prompts/system/eager-task.md +7 -0
- package/src/prompts/system/eager-todo.md +11 -6
- package/src/prompts/system/empty-stop-retry.md +4 -6
- package/src/prompts/system/subagent-system-prompt.md +4 -0
- package/src/prompts/system/system-prompt.md +10 -5
- package/src/prompts/system/title-marker-instruction.md +1 -0
- package/src/prompts/system/title-system-marker.md +16 -0
- package/src/prompts/tools/job.md +1 -0
- package/src/prompts/tools/learn.md +7 -0
- package/src/prompts/tools/manage-skill.md +9 -0
- package/src/prompts/tools/task.md +3 -0
- package/src/registry/agent-registry.ts +30 -0
- package/src/sdk.ts +103 -43
- package/src/secrets/obfuscator.ts +1 -1
- package/src/session/agent-session.ts +331 -318
- package/src/session/agent-storage.ts +18 -9
- package/src/session/history-storage.ts +3 -2
- package/src/session/indexed-session-storage.ts +7 -10
- package/src/session/messages.ts +9 -11
- package/src/session/session-context.ts +352 -0
- package/src/session/session-dump-format.ts +4 -2
- package/src/session/session-entries.ts +194 -0
- package/src/session/session-listing.ts +588 -0
- package/src/session/session-loader.ts +106 -0
- package/src/session/session-manager.ts +968 -3064
- package/src/session/session-migrations.ts +78 -0
- package/src/session/session-paths.ts +193 -0
- package/src/session/session-persistence.ts +131 -0
- package/src/session/session-storage.ts +91 -30
- package/src/session/snapcompact-inline.ts +21 -1
- package/src/session/snapcompact-savings-journal.ts +113 -0
- package/src/session/tool-choice-queue.ts +23 -11
- package/src/slash-commands/builtin-registry.ts +40 -4
- package/src/slash-commands/helpers/logout.ts +88 -0
- package/src/stt/asr-client.ts +520 -0
- package/src/stt/asr-protocol.ts +65 -0
- package/src/stt/asr-worker.ts +790 -0
- package/src/stt/downloader.ts +107 -47
- package/src/stt/endpointer.ts +259 -0
- package/src/stt/index.ts +5 -1
- package/src/stt/models.ts +150 -0
- package/src/stt/recorder.ts +247 -60
- package/src/stt/stt-controller.ts +201 -22
- package/src/stt/transcriber.ts +37 -68
- package/src/stt/wav.ts +173 -0
- package/src/system-prompt.ts +8 -0
- package/src/task/agents.ts +1 -2
- package/src/task/executor.ts +49 -15
- package/src/task/index.ts +60 -6
- package/src/task/render.ts +83 -8
- package/src/task/types.ts +54 -1
- package/src/tools/ask.ts +9 -1
- package/src/tools/ast-edit.ts +1 -1
- package/src/tools/ast-grep.ts +1 -1
- package/src/tools/bash.ts +5 -4
- package/src/tools/browser/cmux/cmux-tab.ts +1264 -0
- package/src/tools/browser/cmux/rpc.ts +156 -0
- package/src/tools/browser/cmux/socket-client.ts +309 -0
- package/src/tools/browser/registry.ts +37 -3
- package/src/tools/browser/render.ts +6 -1
- package/src/tools/browser/tab-protocol.ts +2 -0
- package/src/tools/browser/tab-supervisor.ts +189 -18
- package/src/tools/browser/tab-worker.ts +1 -1
- package/src/tools/browser.ts +16 -1
- package/src/tools/checkpoint.ts +1 -1
- package/src/tools/debug.ts +1 -1
- package/src/tools/eval-render.ts +4 -3
- package/src/tools/eval.ts +11 -6
- package/src/tools/fetch.ts +13 -2
- package/src/tools/find.ts +1 -1
- package/src/tools/gh.ts +1 -1
- package/src/tools/github-cache.ts +2 -1
- package/src/tools/image-gen.ts +1 -1
- package/src/tools/index.ts +43 -5
- package/src/tools/inspect-image.ts +3 -1
- package/src/tools/irc.ts +11 -3
- package/src/tools/job.ts +15 -3
- package/src/tools/learn.ts +144 -0
- package/src/tools/manage-skill.ts +104 -0
- package/src/tools/memory-edit.ts +1 -1
- package/src/tools/memory-recall.ts +1 -1
- package/src/tools/memory-reflect.ts +1 -1
- package/src/tools/memory-retain.ts +1 -1
- package/src/tools/plan-mode-guard.ts +53 -19
- package/src/tools/read.ts +8 -2
- package/src/tools/render-mermaid.ts +1 -1
- package/src/tools/renderers.ts +7 -11
- package/src/tools/report-tool-issue.ts +3 -2
- package/src/tools/resolve.ts +1 -1
- package/src/tools/review.ts +1 -1
- package/src/tools/search-tool-bm25.ts +1 -1
- package/src/tools/search.ts +1 -1
- package/src/tools/ssh.ts +5 -4
- package/src/tools/todo.ts +2 -2
- package/src/tools/tts.ts +204 -93
- package/src/tools/write.ts +19 -3
- package/src/tts/downloader.ts +64 -0
- package/src/tts/index.ts +8 -0
- package/src/tts/models.ts +137 -0
- package/src/tts/player.ts +137 -0
- package/src/tts/runtime.ts +21 -0
- package/src/tts/streaming-player.ts +266 -0
- package/src/tts/tts-client.ts +647 -0
- package/src/tts/tts-protocol.ts +60 -0
- package/src/tts/tts-worker.ts +497 -0
- package/src/tts/vocalizer.ts +162 -0
- package/src/tts/wav.ts +58 -0
- package/src/utils/clipboard.ts +35 -18
- package/src/utils/image-loading.ts +35 -4
- package/src/utils/thinking-display.ts +37 -0
- package/src/utils/title-generator.ts +48 -5
- package/src/utils/tool-choice.ts +16 -0
- package/src/utils/tools-manager.test.ts +25 -0
- package/src/utils/tools-manager.ts +19 -1
- package/src/web/scrapers/github.ts +96 -0
- package/src/web/search/index.ts +14 -1
- package/src/web/search/providers/searxng.ts +13 -1
- package/dist/types/cli/list-models.d.ts +0 -30
- package/dist/types/stt/setup.d.ts +0 -18
- package/src/cli/list-models.ts +0 -194
- package/src/stt/setup.ts +0 -52
- package/src/stt/transcribe.py +0 -70
package/src/tts/wav.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const WAV_HEADER_BYTES = 44;
|
|
2
|
+
const PCM16_FORMAT = 1;
|
|
3
|
+
const BITS_PER_SAMPLE = 16;
|
|
4
|
+
const INT16_MAX = 32_767;
|
|
5
|
+
const INT16_MIN = -32_768;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Assemble a mono PCM16 WAV byte buffer from Float32 PCM samples (the shape
|
|
9
|
+
* transformers.js `RawAudio` emits: normalized [-1, 1] amplitudes plus a sample
|
|
10
|
+
* rate). No external encoder is involved — we write a canonical 44-byte RIFF/
|
|
11
|
+
* WAVE header followed by little-endian signed 16-bit samples. Samples are
|
|
12
|
+
* clamped before quantization so out-of-range float values do not wrap.
|
|
13
|
+
*/
|
|
14
|
+
export function encodeWav(samples: Float32Array, sampleRate: number): Uint8Array {
|
|
15
|
+
const channels = 1;
|
|
16
|
+
const byteRate = sampleRate * channels * (BITS_PER_SAMPLE / 8);
|
|
17
|
+
const blockAlign = channels * (BITS_PER_SAMPLE / 8);
|
|
18
|
+
const dataBytes = samples.length * (BITS_PER_SAMPLE / 8);
|
|
19
|
+
const buffer = new ArrayBuffer(WAV_HEADER_BYTES + dataBytes);
|
|
20
|
+
const view = new DataView(buffer);
|
|
21
|
+
|
|
22
|
+
// RIFF chunk descriptor
|
|
23
|
+
writeAscii(view, 0, "RIFF");
|
|
24
|
+
view.setUint32(4, WAV_HEADER_BYTES - 8 + dataBytes, true); // file size minus the first 8 bytes
|
|
25
|
+
writeAscii(view, 8, "WAVE");
|
|
26
|
+
|
|
27
|
+
// fmt sub-chunk
|
|
28
|
+
writeAscii(view, 12, "fmt ");
|
|
29
|
+
view.setUint32(16, 16, true); // PCM fmt chunk size
|
|
30
|
+
view.setUint16(20, PCM16_FORMAT, true);
|
|
31
|
+
view.setUint16(22, channels, true);
|
|
32
|
+
view.setUint32(24, sampleRate, true);
|
|
33
|
+
view.setUint32(28, byteRate, true);
|
|
34
|
+
view.setUint16(32, blockAlign, true);
|
|
35
|
+
view.setUint16(34, BITS_PER_SAMPLE, true);
|
|
36
|
+
|
|
37
|
+
// data sub-chunk
|
|
38
|
+
writeAscii(view, 36, "data");
|
|
39
|
+
view.setUint32(40, dataBytes, true);
|
|
40
|
+
|
|
41
|
+
let offset = WAV_HEADER_BYTES;
|
|
42
|
+
for (let i = 0; i < samples.length; i += 1) {
|
|
43
|
+
const sample = samples[i]!;
|
|
44
|
+
const clamped = sample > 1 ? 1 : sample < -1 ? -1 : sample;
|
|
45
|
+
const quantized =
|
|
46
|
+
clamped < 0
|
|
47
|
+
? Math.max(INT16_MIN, Math.round(clamped * -INT16_MIN))
|
|
48
|
+
: Math.min(INT16_MAX, Math.round(clamped * INT16_MAX));
|
|
49
|
+
view.setInt16(offset, quantized, true);
|
|
50
|
+
offset += 2;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return new Uint8Array(buffer);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function writeAscii(view: DataView, offset: number, text: string): void {
|
|
57
|
+
for (let i = 0; i < text.length; i += 1) view.setUint8(offset + i, text.charCodeAt(i));
|
|
58
|
+
}
|
package/src/utils/clipboard.ts
CHANGED
|
@@ -66,9 +66,10 @@ export async function copyToClipboard(text: string): Promise<void> {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
// PowerShell one-liner that emits the clipboard image as base64-encoded
|
|
70
|
-
// stdout, or nothing when the clipboard does not hold image data. Used
|
|
71
|
-
//
|
|
69
|
+
// PowerShell one-liner that emits the Windows clipboard image as base64-encoded
|
|
70
|
+
// PNG on stdout, or nothing when the clipboard does not hold image data. Used
|
|
71
|
+
// for native Windows fallback and WSL interop because arboard can miss host
|
|
72
|
+
// clipboard image payloads in those terminal paths.
|
|
72
73
|
const POWERSHELL_IMAGE_SCRIPT = `
|
|
73
74
|
$ErrorActionPreference = 'Stop'
|
|
74
75
|
Add-Type -AssemblyName System.Windows.Forms
|
|
@@ -84,31 +85,36 @@ if ($img -ne $null) {
|
|
|
84
85
|
const POWERSHELL_TIMEOUT_MS = 8000;
|
|
85
86
|
|
|
86
87
|
/**
|
|
87
|
-
* Read
|
|
88
|
+
* Read an image through the Windows host's PowerShell.
|
|
88
89
|
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
90
|
+
* Native Windows uses this as a fallback when arboard reports no image or
|
|
91
|
+
* cannot access the clipboard. WSLg exposes a Wayland socket but no native
|
|
92
|
+
* clipboard image transport, so arboard returns `ContentNotAvailable` there;
|
|
93
|
+
* PowerShell, reached via WSL interop, can read the Windows clipboard directly
|
|
94
|
+
* and round-trip the bitmap as PNG.
|
|
92
95
|
*
|
|
93
96
|
* Returns null when no image is on the clipboard, the host PowerShell is
|
|
94
97
|
* missing, or the bridge times out.
|
|
95
98
|
*/
|
|
96
99
|
async function readImageViaPowerShell(): Promise<ClipboardImage | null> {
|
|
97
100
|
try {
|
|
98
|
-
const proc = Bun.spawn(
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
const proc = Bun.spawn(
|
|
102
|
+
["powershell.exe", "-NoProfile", "-NonInteractive", "-Sta", "-Command", POWERSHELL_IMAGE_SCRIPT],
|
|
103
|
+
{
|
|
104
|
+
stdout: "pipe",
|
|
105
|
+
stderr: "ignore",
|
|
106
|
+
stdin: "ignore",
|
|
107
|
+
},
|
|
108
|
+
);
|
|
103
109
|
const timer = setTimeout(() => proc.kill(), POWERSHELL_TIMEOUT_MS);
|
|
104
110
|
let stdout = "";
|
|
105
111
|
try {
|
|
106
112
|
stdout = await new Response(proc.stdout).text();
|
|
107
113
|
await proc.exited;
|
|
108
114
|
} catch (err) {
|
|
109
|
-
// powershell.exe
|
|
110
|
-
// doesn't reap cleanly,
|
|
111
|
-
//
|
|
115
|
+
// powershell.exe can be a Windows process reached either natively or
|
|
116
|
+
// over WSL interop; if it doesn't reap cleanly, report no image instead
|
|
117
|
+
// of surfacing an opaque bridge failure to the prompt.
|
|
112
118
|
logger.warn("clipboard: powershell read failed", { error: String(err) });
|
|
113
119
|
return null;
|
|
114
120
|
} finally {
|
|
@@ -179,9 +185,10 @@ async function readTextViaPowerShell(): Promise<string | null> {
|
|
|
179
185
|
* Read an image from the system clipboard.
|
|
180
186
|
*
|
|
181
187
|
* Returns null on Termux (no image clipboard support) or when no display
|
|
182
|
-
* server is available (headless/SSH without forwarding). Under
|
|
183
|
-
* Windows clipboard is reached through `powershell.exe
|
|
184
|
-
*
|
|
188
|
+
* server is available (headless/SSH without forwarding). Under native Windows
|
|
189
|
+
* and WSL, the Windows clipboard is also reached through `powershell.exe`
|
|
190
|
+
* because terminal clipboard paths can leave image payloads invisible to the
|
|
191
|
+
* native bridge.
|
|
185
192
|
*
|
|
186
193
|
* @returns PNG payload or null when no image is available.
|
|
187
194
|
*/
|
|
@@ -198,6 +205,16 @@ export async function readImageFromClipboard(): Promise<ClipboardImage | null> {
|
|
|
198
205
|
// no display, so arboard would reject anyway.
|
|
199
206
|
}
|
|
200
207
|
|
|
208
|
+
if (process.platform === "win32") {
|
|
209
|
+
try {
|
|
210
|
+
const image = await native.readImageFromClipboard();
|
|
211
|
+
if (image) return image;
|
|
212
|
+
} catch (err) {
|
|
213
|
+
logger.warn("clipboard: native Windows image read failed", { error: String(err) });
|
|
214
|
+
}
|
|
215
|
+
return await readImageViaPowerShell();
|
|
216
|
+
}
|
|
217
|
+
|
|
201
218
|
if (!hasDisplay()) {
|
|
202
219
|
return null;
|
|
203
220
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
|
-
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { ImageContent, Model } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { formatBytes, readImageMetadata, SUPPORTED_IMAGE_MIME_TYPES } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { resolveReadPath } from "../tools/path-utils";
|
|
5
5
|
import { formatDimensionNote, type ImageResizeOptions, resizeImage } from "./image-resize";
|
|
@@ -7,6 +7,26 @@ import { formatDimensionNote, type ImageResizeOptions, resizeImage } from "./ima
|
|
|
7
7
|
export const MAX_IMAGE_INPUT_BYTES = 20 * 1024 * 1024;
|
|
8
8
|
export const SUPPORTED_INPUT_IMAGE_MIME_TYPES = SUPPORTED_IMAGE_MIME_TYPES;
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Ollama and its local-backend family decode image input through llama.cpp /
|
|
12
|
+
* `stb_image`, which is compiled without WebP support, so a WebP upload fails
|
|
13
|
+
* with an opaque HTTP 400. Detect those models so the resize pipeline encodes
|
|
14
|
+
* to PNG/JPEG instead — the automatic equivalent of `OMP_NO_WEBP=1`.
|
|
15
|
+
*/
|
|
16
|
+
export function modelLacksWebpSupport(model: Pick<Model, "provider" | "api"> | undefined): boolean {
|
|
17
|
+
if (!model) return false;
|
|
18
|
+
return model.provider === "ollama" || model.provider === "ollama-cloud" || model.api === "ollama-chat";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* `true` when `model` cannot decode WebP, otherwise `undefined` so the
|
|
23
|
+
* `OMP_NO_WEBP` env fallback in {@link resizeImage} still applies. Feed straight
|
|
24
|
+
* into {@link ImageResizeOptions.excludeWebP}.
|
|
25
|
+
*/
|
|
26
|
+
export function webpExclusionForModel(model: Pick<Model, "provider" | "api"> | undefined): true | undefined {
|
|
27
|
+
return modelLacksWebpSupport(model) ? true : undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
10
30
|
export interface LoadImageInputOptions {
|
|
11
31
|
path: string;
|
|
12
32
|
cwd: string;
|
|
@@ -14,6 +34,8 @@ export interface LoadImageInputOptions {
|
|
|
14
34
|
maxBytes?: number;
|
|
15
35
|
resolvedPath?: string;
|
|
16
36
|
detectedMimeType?: string;
|
|
37
|
+
/** Force non-WebP output (e.g. for Ollama). Leave unset to honor `OMP_NO_WEBP`. */
|
|
38
|
+
excludeWebP?: boolean;
|
|
17
39
|
}
|
|
18
40
|
|
|
19
41
|
export interface LoadedImageInput {
|
|
@@ -51,6 +73,8 @@ export async function ensureSupportedImageInput(image: ImageContent): Promise<Im
|
|
|
51
73
|
}
|
|
52
74
|
|
|
53
75
|
export interface NormalizeModelContextImagesOptions {
|
|
76
|
+
/** Model the images are bound for; used to derive encoder constraints (WebP exclusion for Ollama). */
|
|
77
|
+
model?: Model;
|
|
54
78
|
resize?: ImageResizeOptions;
|
|
55
79
|
}
|
|
56
80
|
|
|
@@ -66,10 +90,13 @@ export async function normalizeModelContextImages(
|
|
|
66
90
|
options?: NormalizeModelContextImagesOptions,
|
|
67
91
|
): Promise<ImageContent[] | undefined> {
|
|
68
92
|
if (!images || images.length === 0) return undefined;
|
|
93
|
+
const resize: ImageResizeOptions | undefined = modelLacksWebpSupport(options?.model)
|
|
94
|
+
? { ...options?.resize, excludeWebP: true }
|
|
95
|
+
: options?.resize;
|
|
69
96
|
const normalized: ImageContent[] = [];
|
|
70
97
|
for (const image of images) {
|
|
71
98
|
try {
|
|
72
|
-
const resized = await resizeImage(image,
|
|
99
|
+
const resized = await resizeImage(image, resize);
|
|
73
100
|
normalized.push({ type: "image", data: resized.data, mimeType: resized.mimeType });
|
|
74
101
|
} catch {
|
|
75
102
|
// Preserve existing caller behavior for decode/resize failures: keep the
|
|
@@ -104,9 +131,13 @@ export async function loadImageInput(options: LoadImageInputOptions): Promise<Lo
|
|
|
104
131
|
let outputBytes = inputBuffer.byteLength;
|
|
105
132
|
let dimensionNote: string | undefined;
|
|
106
133
|
|
|
107
|
-
|
|
134
|
+
const shouldReencodeWebP = options.excludeWebP === true && mimeType === "image/webp";
|
|
135
|
+
if (options.autoResize || shouldReencodeWebP) {
|
|
108
136
|
try {
|
|
109
|
-
const resized = await resizeImage(
|
|
137
|
+
const resized = await resizeImage(
|
|
138
|
+
{ type: "image", data: outputData, mimeType },
|
|
139
|
+
{ excludeWebP: options.excludeWebP },
|
|
140
|
+
);
|
|
110
141
|
outputData = resized.data;
|
|
111
142
|
outputMimeType = resized.mimeType;
|
|
112
143
|
outputBytes = resized.buffer.byteLength;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
2
|
+
|
|
3
|
+
type AssistantContentBlock = AssistantMessage["content"][number];
|
|
4
|
+
type ThinkingBlock = Extract<AssistantContentBlock, { type: "thinking" }>;
|
|
5
|
+
|
|
6
|
+
function isDotOnlyThinking(text: string): boolean {
|
|
7
|
+
let sawDot = false;
|
|
8
|
+
for (let i = 0; i < text.length; i++) {
|
|
9
|
+
const code = text.charCodeAt(i);
|
|
10
|
+
if (code === 0x2e || code === 0x2026) {
|
|
11
|
+
sawDot = true;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (code === 0x20 || code === 0x09 || code === 0x0a || code === 0x0d) continue;
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
return sawDot;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns the operator-visible thinking text for a block.
|
|
22
|
+
*
|
|
23
|
+
* Some OpenAI-compatible reasoning gateways require a non-empty
|
|
24
|
+
* `reasoning_content` field on historical assistant tool-call turns even when
|
|
25
|
+
* the model did not emit any reasoning. The provider adapter uses a single dot
|
|
26
|
+
* as the wire-only placeholder those gateways accept; if that value is later
|
|
27
|
+
* replayed or echoed as a thinking block, it should not render as model thought.
|
|
28
|
+
*/
|
|
29
|
+
export function getVisibleThinkingText(block: ThinkingBlock): string {
|
|
30
|
+
const text = block.thinking.trim();
|
|
31
|
+
if (text.length === 0) return "";
|
|
32
|
+
return isDotOnlyThinking(text) ? "" : text;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function hasVisibleThinking(block: ThinkingBlock): boolean {
|
|
36
|
+
return getVisibleThinkingText(block).length > 0;
|
|
37
|
+
}
|
|
@@ -9,12 +9,16 @@ import type { ModelRegistry } from "../config/model-registry";
|
|
|
9
9
|
|
|
10
10
|
import { resolveRoleSelection } from "../config/model-resolver";
|
|
11
11
|
import type { Settings } from "../config/settings";
|
|
12
|
+
import titleMarkerInstruction from "../prompts/system/title-marker-instruction.md" with { type: "text" };
|
|
12
13
|
import titleSystemPrompt from "../prompts/system/title-system.md" with { type: "text" };
|
|
14
|
+
import titleMarkerSystemPrompt from "../prompts/system/title-system-marker.md" with { type: "text" };
|
|
13
15
|
import { ONLINE_TINY_TITLE_MODEL_KEY } from "../tiny/models";
|
|
14
16
|
import { formatTitleUserMessage, isLowSignalTitleInput, normalizeGeneratedTitle } from "../tiny/text";
|
|
15
17
|
import { tinyTitleClient } from "../tiny/title-client";
|
|
16
18
|
|
|
17
19
|
const TITLE_SYSTEM_PROMPT = prompt.render(titleSystemPrompt);
|
|
20
|
+
const TITLE_MARKER_SYSTEM_PROMPT = prompt.render(titleMarkerSystemPrompt);
|
|
21
|
+
const TITLE_MARKER_INSTRUCTION = prompt.render(titleMarkerInstruction);
|
|
18
22
|
|
|
19
23
|
const DEFAULT_TERMINAL_TITLE = "π";
|
|
20
24
|
const TERMINAL_TITLE_CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f]/g;
|
|
@@ -41,6 +45,30 @@ const setTitleTool: Tool = {
|
|
|
41
45
|
},
|
|
42
46
|
};
|
|
43
47
|
|
|
48
|
+
/** Matches the title a tool-choice-less model wraps in `<title>...</title>`. */
|
|
49
|
+
const TITLE_MARKER_RE = /<title>([\s\S]*?)<\/title>/i;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Whether the model honors a forced `tool_choice` so the `set_title` tool can be
|
|
53
|
+
* required. Providers/models that reject forced tool calls (chat-completions
|
|
54
|
+
* hosts without `tool_choice` support, Claude Fable/Mythos) can't be made to
|
|
55
|
+
* emit a structured call, so the caller falls back to marker-wrapped text.
|
|
56
|
+
*/
|
|
57
|
+
function modelSupportsForcedToolChoice(model: Model<Api>): boolean {
|
|
58
|
+
// `compat` is a union across APIs and `supportsToolChoice` lives only on the
|
|
59
|
+
// OpenAI-completions variant, so read both flags through a structural view.
|
|
60
|
+
const compat = model.compat as { supportsToolChoice?: boolean; supportsForcedToolChoice?: boolean } | undefined;
|
|
61
|
+
if (!compat) return true;
|
|
62
|
+
// A forced tool call first requires sending `tool_choice` at all. Hosts that
|
|
63
|
+
// drop the parameter entirely (`supportsToolChoice: false`, e.g. direct
|
|
64
|
+
// DeepSeek reasoning) can never be forced even when they otherwise accept
|
|
65
|
+
// forced values, so this veto wins over `supportsForcedToolChoice`.
|
|
66
|
+
if (compat.supportsToolChoice === false) return false;
|
|
67
|
+
if (typeof compat.supportsForcedToolChoice === "boolean") return compat.supportsForcedToolChoice;
|
|
68
|
+
if (typeof compat.supportsToolChoice === "boolean") return compat.supportsToolChoice;
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
44
72
|
function getTitleModel(registry: ModelRegistry, settings: Settings, currentModel?: Model<Api>): Model<Api> | undefined {
|
|
45
73
|
const availableModels = registry.getAvailable();
|
|
46
74
|
if (availableModels.length === 0) return undefined;
|
|
@@ -221,7 +249,16 @@ export async function generateTitleOnline(
|
|
|
221
249
|
}
|
|
222
250
|
|
|
223
251
|
const titleSystemPrompt = customSystemPrompt?.trim() || undefined;
|
|
224
|
-
|
|
252
|
+
// Some providers can't be forced to call a tool — chat-completions hosts
|
|
253
|
+
// without `tool_choice` support, Claude Fable/Mythos — so a required
|
|
254
|
+
// `set_title` call never arrives. For those, ask the model to wrap the title
|
|
255
|
+
// in `<title>...</title>` markers and parse it from text instead.
|
|
256
|
+
const useForcedTool = modelSupportsForcedToolChoice(model);
|
|
257
|
+
const systemPrompt = useForcedTool
|
|
258
|
+
? [titleSystemPrompt ?? TITLE_SYSTEM_PROMPT]
|
|
259
|
+
: titleSystemPrompt
|
|
260
|
+
? [titleSystemPrompt, TITLE_MARKER_INSTRUCTION]
|
|
261
|
+
: [TITLE_MARKER_SYSTEM_PROMPT];
|
|
225
262
|
const userMessage = formatTitleUserMessage(firstMessage);
|
|
226
263
|
const modelName = `${model.provider}/${model.id}`;
|
|
227
264
|
const modelContext = {
|
|
@@ -253,15 +290,15 @@ export async function generateTitleOnline(
|
|
|
253
290
|
const response = await completeSimple(
|
|
254
291
|
model,
|
|
255
292
|
{
|
|
256
|
-
systemPrompt
|
|
293
|
+
systemPrompt,
|
|
257
294
|
messages: [{ role: "user", content: userMessage, timestamp: Date.now() }],
|
|
258
|
-
tools: [setTitleTool],
|
|
295
|
+
tools: useForcedTool ? [setTitleTool] : undefined,
|
|
259
296
|
},
|
|
260
297
|
{
|
|
261
298
|
apiKey: registry.resolver(model, sessionId),
|
|
262
299
|
maxTokens,
|
|
263
300
|
disableReasoning: true,
|
|
264
|
-
toolChoice: { type: "tool", name: SET_TITLE_TOOL_NAME },
|
|
301
|
+
toolChoice: useForcedTool ? { type: "tool", name: SET_TITLE_TOOL_NAME } : undefined,
|
|
265
302
|
metadata,
|
|
266
303
|
signal,
|
|
267
304
|
},
|
|
@@ -319,7 +356,13 @@ function extractGeneratedTitle(contentBlocks: AssistantMessage["content"]): stri
|
|
|
319
356
|
textTitle += content.text;
|
|
320
357
|
}
|
|
321
358
|
}
|
|
322
|
-
|
|
359
|
+
// Tool-choice-less models are asked to wrap the title in <title>...</title>,
|
|
360
|
+
// but stay lenient: prefer the marker when the model closed it, otherwise
|
|
361
|
+
// accept a plain sentence after stripping any stray/unclosed tag fragment
|
|
362
|
+
// (e.g. output truncated before the closing tag).
|
|
363
|
+
const marker = TITLE_MARKER_RE.exec(textTitle);
|
|
364
|
+
if (marker) return marker[1].trim();
|
|
365
|
+
return textTitle.replace(/<\/?title>/gi, "").trim();
|
|
323
366
|
}
|
|
324
367
|
|
|
325
368
|
/**
|
package/src/utils/tool-choice.ts
CHANGED
|
@@ -31,3 +31,19 @@ export function buildNamedToolChoice(toolName: string, model?: Model<Api>): Tool
|
|
|
31
31
|
|
|
32
32
|
return undefined;
|
|
33
33
|
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Whether the given tool choice can be satisfied by the active tool set for the
|
|
37
|
+
* upcoming turn. Non-named choices (`"none"`, `"required"`, etc.) do not name a
|
|
38
|
+
* specific tool and are therefore always active.
|
|
39
|
+
*/
|
|
40
|
+
export function isToolChoiceActive(toolChoice: ToolChoice | undefined, tools: readonly { name: string }[]): boolean {
|
|
41
|
+
if (!toolChoice || typeof toolChoice === "string") return true;
|
|
42
|
+
const name =
|
|
43
|
+
toolChoice.type === "tool"
|
|
44
|
+
? toolChoice.name
|
|
45
|
+
: "function" in toolChoice
|
|
46
|
+
? toolChoice.function.name
|
|
47
|
+
: toolChoice.name;
|
|
48
|
+
return tools.some(tool => tool.name === name);
|
|
49
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import { ffmpegAssetName } from "./tools-manager";
|
|
3
|
+
|
|
4
|
+
describe("ffmpegAssetName", () => {
|
|
5
|
+
it("maps supported platform/arch pairs to direct-binary asset names", () => {
|
|
6
|
+
expect(ffmpegAssetName("b6.1.1", "darwin", "arm64")).toBe("ffmpeg-darwin-arm64");
|
|
7
|
+
expect(ffmpegAssetName("b6.1.1", "darwin", "x64")).toBe("ffmpeg-darwin-x64");
|
|
8
|
+
expect(ffmpegAssetName("b6.1.1", "linux", "arm64")).toBe("ffmpeg-linux-arm64");
|
|
9
|
+
expect(ffmpegAssetName("b6.1.1", "linux", "x64")).toBe("ffmpeg-linux-x64");
|
|
10
|
+
expect(ffmpegAssetName("b6.1.1", "win32", "x64")).toBe("ffmpeg-win32-x64");
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("returns null for win32 on arm64 (no static asset published)", () => {
|
|
14
|
+
expect(ffmpegAssetName("b6.1.1", "win32", "arm64")).toBeNull();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("returns null for unsupported arch", () => {
|
|
18
|
+
expect(ffmpegAssetName("b6.1.1", "darwin", "ia32")).toBeNull();
|
|
19
|
+
expect(ffmpegAssetName("b6.1.1", "linux", "ppc64")).toBeNull();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("returns null for unsupported platform", () => {
|
|
23
|
+
expect(ffmpegAssetName("b6.1.1", "freebsd", "x64")).toBeNull();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -16,6 +16,16 @@ interface ToolConfig {
|
|
|
16
16
|
getAssetName: (version: string, plat: string, architecture: string) => string | null;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// ffmpeg static-binary asset names (eugeneware/ffmpeg-static direct binaries).
|
|
20
|
+
// Maps node arch (arm64|x64) only; everything else is unsupported.
|
|
21
|
+
export function ffmpegAssetName(_version: string, plat: string, architecture: string): string | null {
|
|
22
|
+
if (architecture !== "arm64" && architecture !== "x64") return null;
|
|
23
|
+
if (plat === "darwin") return `ffmpeg-darwin-${architecture}`;
|
|
24
|
+
if (plat === "linux") return `ffmpeg-linux-${architecture}`;
|
|
25
|
+
if (plat === "win32") return architecture === "x64" ? "ffmpeg-win32-x64" : null;
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
19
29
|
const TOOLS: Record<string, ToolConfig> = {
|
|
20
30
|
sd: {
|
|
21
31
|
name: "sd",
|
|
@@ -72,6 +82,14 @@ const TOOLS: Record<string, ToolConfig> = {
|
|
|
72
82
|
return null;
|
|
73
83
|
},
|
|
74
84
|
},
|
|
85
|
+
ffmpeg: {
|
|
86
|
+
name: "ffmpeg",
|
|
87
|
+
repo: "eugeneware/ffmpeg-static",
|
|
88
|
+
binaryName: "ffmpeg",
|
|
89
|
+
tagPrefix: "",
|
|
90
|
+
isDirectBinary: true,
|
|
91
|
+
getAssetName: ffmpegAssetName,
|
|
92
|
+
},
|
|
75
93
|
};
|
|
76
94
|
|
|
77
95
|
// CLI packages installed via uv/pip
|
|
@@ -89,7 +107,7 @@ const PYTHON_TOOLS: Record<string, PythonPackageToolConfig> = {
|
|
|
89
107
|
},
|
|
90
108
|
};
|
|
91
109
|
|
|
92
|
-
export type ToolName = "sd" | "sg" | "yt-dlp" | "trafilatura";
|
|
110
|
+
export type ToolName = "sd" | "sg" | "yt-dlp" | "trafilatura" | "ffmpeg";
|
|
93
111
|
|
|
94
112
|
// Get the path to a tool (system-wide or in our tools dir)
|
|
95
113
|
export function getToolPath(tool: ToolName): string | null {
|
|
@@ -7,6 +7,7 @@ interface GitHubUrl {
|
|
|
7
7
|
| "blob"
|
|
8
8
|
| "tree"
|
|
9
9
|
| "repo"
|
|
10
|
+
| "commit"
|
|
10
11
|
| "issue"
|
|
11
12
|
| "issues"
|
|
12
13
|
| "pull"
|
|
@@ -56,6 +57,11 @@ export function parseGitHubUrl(url: string): GitHubUrl | null {
|
|
|
56
57
|
const [ref, ...pathParts] = subParts;
|
|
57
58
|
return { type: section, owner, repo, ref, path: pathParts.join("/") };
|
|
58
59
|
}
|
|
60
|
+
case "commit":
|
|
61
|
+
if (subParts.length > 0 && subParts[0]) {
|
|
62
|
+
return { type: "commit", owner, repo, ref: subParts[0] };
|
|
63
|
+
}
|
|
64
|
+
return { type: "other", owner, repo };
|
|
59
65
|
case "issues":
|
|
60
66
|
if (subParts.length > 0 && /^\d+$/.test(subParts[0])) {
|
|
61
67
|
return { type: "issue", owner, repo, number: parseInt(subParts[0], 10) };
|
|
@@ -233,6 +239,87 @@ async function renderGitHubIssue(
|
|
|
233
239
|
return { content: md, ok: true };
|
|
234
240
|
}
|
|
235
241
|
|
|
242
|
+
interface GitHubCommitFile {
|
|
243
|
+
filename: string;
|
|
244
|
+
status: string;
|
|
245
|
+
additions: number;
|
|
246
|
+
deletions: number;
|
|
247
|
+
changes: number;
|
|
248
|
+
patch?: string;
|
|
249
|
+
previous_filename?: string;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Render a GitHub commit (metadata, message, and per-file diff) to markdown.
|
|
254
|
+
*
|
|
255
|
+
* The commits API (`/repos/{owner}/{repo}/commits/{ref}`) returns the full
|
|
256
|
+
* unified diff inline via `files[].patch`, so a single request yields both the
|
|
257
|
+
* summary and the diff. Binary files have no `patch` and are flagged instead.
|
|
258
|
+
*/
|
|
259
|
+
async function renderGitHubCommit(
|
|
260
|
+
gh: GitHubUrl,
|
|
261
|
+
timeout: number,
|
|
262
|
+
signal?: AbortSignal,
|
|
263
|
+
): Promise<{ content: string; ok: boolean }> {
|
|
264
|
+
const result = await fetchGitHubApi(`/repos/${gh.owner}/${gh.repo}/commits/${gh.ref}`, timeout, signal);
|
|
265
|
+
if (!result.ok || !result.data) return { content: "", ok: false };
|
|
266
|
+
|
|
267
|
+
const commit = result.data as {
|
|
268
|
+
sha: string;
|
|
269
|
+
html_url: string;
|
|
270
|
+
commit: {
|
|
271
|
+
author?: { name?: string; date?: string } | null;
|
|
272
|
+
committer?: { name?: string; date?: string } | null;
|
|
273
|
+
message: string;
|
|
274
|
+
};
|
|
275
|
+
author?: { login: string } | null;
|
|
276
|
+
committer?: { login: string } | null;
|
|
277
|
+
parents?: Array<{ sha: string }>;
|
|
278
|
+
stats?: { total?: number; additions?: number; deletions?: number };
|
|
279
|
+
files?: GitHubCommitFile[];
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const message = commit.commit.message ?? "";
|
|
283
|
+
const [subject, ...bodyLines] = message.split("\n");
|
|
284
|
+
const authorName = commit.author?.login ? `@${commit.author.login}` : (commit.commit.author?.name ?? "unknown");
|
|
285
|
+
const authoredAt = commit.commit.author?.date ?? "";
|
|
286
|
+
|
|
287
|
+
let md = `# ${subject || commit.sha.slice(0, 7)}\n\n`;
|
|
288
|
+
md += `**${commit.sha.slice(0, 12)}** · authored by ${authorName}`;
|
|
289
|
+
if (authoredAt) md += ` · ${authoredAt}`;
|
|
290
|
+
md += `\n`;
|
|
291
|
+
if (commit.stats) {
|
|
292
|
+
const { additions = 0, deletions = 0 } = commit.stats;
|
|
293
|
+
const fileCount = commit.files?.length ?? 0;
|
|
294
|
+
md += `${fileCount} file${fileCount === 1 ? "" : "s"} changed · +${additions} −${deletions}\n`;
|
|
295
|
+
}
|
|
296
|
+
if (commit.parents && commit.parents.length > 0) {
|
|
297
|
+
md += `Parents: ${commit.parents.map(p => p.sha.slice(0, 12)).join(", ")}\n`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const body = bodyLines.join("\n").trim();
|
|
301
|
+
if (body) {
|
|
302
|
+
md += `\n${body}\n`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const files = commit.files ?? [];
|
|
306
|
+
if (files.length > 0) {
|
|
307
|
+
md += `\n---\n\n## Files (${files.length})\n\n`;
|
|
308
|
+
for (const file of files) {
|
|
309
|
+
const name = file.previous_filename ? `${file.previous_filename} → ${file.filename}` : file.filename;
|
|
310
|
+
md += `### ${name}\n\n`;
|
|
311
|
+
md += `${file.status} · +${file.additions} −${file.deletions}\n\n`;
|
|
312
|
+
if (file.patch) {
|
|
313
|
+
md += `\`\`\`diff\n${file.patch}\n\`\`\`\n\n`;
|
|
314
|
+
} else {
|
|
315
|
+
md += `*No textual diff (binary or too large).*\n\n`;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return { content: md, ok: true };
|
|
321
|
+
}
|
|
322
|
+
|
|
236
323
|
/**
|
|
237
324
|
* Render GitHub issues list to markdown
|
|
238
325
|
*/
|
|
@@ -647,6 +734,15 @@ export const handleGitHub: SpecialHandler = async (
|
|
|
647
734
|
break;
|
|
648
735
|
}
|
|
649
736
|
|
|
737
|
+
case "commit": {
|
|
738
|
+
notes.push(`Fetched via GitHub API`);
|
|
739
|
+
const result = await renderGitHubCommit(gh, timeout, signal);
|
|
740
|
+
if (result.ok) {
|
|
741
|
+
return buildResult(result.content, { url, method: "github-commit", fetchedAt, notes });
|
|
742
|
+
}
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
|
|
650
746
|
case "issue":
|
|
651
747
|
case "pull": {
|
|
652
748
|
notes.push(`Fetched via GitHub API`);
|
package/src/web/search/index.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
8
8
|
import type { AuthStorage } from "@oh-my-pi/pi-ai";
|
|
9
9
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
10
|
-
import
|
|
10
|
+
import { z } from "zod/v4";
|
|
11
11
|
import type { CustomTool, CustomToolContext, RenderResultOptions } from "../../extensibility/custom-tools/types";
|
|
12
12
|
import type { Theme } from "../../modes/theme/theme";
|
|
13
13
|
import webSearchSystemPrompt from "../../prompts/system/web-search.md" with { type: "text" };
|
|
@@ -115,6 +115,15 @@ function formatForLLM(response: SearchResponse): string {
|
|
|
115
115
|
return parts.join("\n");
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
function hasRenderableSearchContent(response: SearchResponse): boolean {
|
|
119
|
+
if (response.answer?.trim()) return true;
|
|
120
|
+
if (response.sources.length > 0) return true;
|
|
121
|
+
if (response.citations?.length) return true;
|
|
122
|
+
if (response.relatedQuestions?.some(question => question.trim())) return true;
|
|
123
|
+
if (response.searchQueries?.some(query => query.trim())) return true;
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
118
127
|
interface ExecuteSearchOptions {
|
|
119
128
|
authStorage: AuthStorage;
|
|
120
129
|
sessionId?: string;
|
|
@@ -162,6 +171,10 @@ async function executeSearch(
|
|
|
162
171
|
sessionId,
|
|
163
172
|
});
|
|
164
173
|
|
|
174
|
+
if (!hasRenderableSearchContent(response)) {
|
|
175
|
+
throw new SearchProviderError(provider.id, `${provider.label} returned no renderable search content.`, 204);
|
|
176
|
+
}
|
|
177
|
+
|
|
165
178
|
const text = formatForLLM(response);
|
|
166
179
|
|
|
167
180
|
return {
|
|
@@ -281,9 +281,21 @@ export async function searchSearXNG(params: {
|
|
|
281
281
|
});
|
|
282
282
|
}
|
|
283
283
|
|
|
284
|
+
const limitedSources = sources.slice(0, numResults);
|
|
285
|
+
if (limitedSources.length === 0 && response.unresponsive_engines?.length) {
|
|
286
|
+
const upstreamFailures = response.unresponsive_engines
|
|
287
|
+
.map(([engine, reason]) => `${engine}: ${reason}`)
|
|
288
|
+
.join("; ");
|
|
289
|
+
throw new SearchProviderError(
|
|
290
|
+
"searxng",
|
|
291
|
+
`SearXNG returned no usable results; upstream engines failed: ${upstreamFailures}`,
|
|
292
|
+
503,
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
284
296
|
return {
|
|
285
297
|
provider: "searxng",
|
|
286
|
-
sources:
|
|
298
|
+
sources: limitedSources,
|
|
287
299
|
relatedQuestions: response.suggestions?.length ? response.suggestions : undefined,
|
|
288
300
|
};
|
|
289
301
|
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { ModelRegistry } from "../config/model-registry";
|
|
2
|
-
/**
|
|
3
|
-
* List available models, optionally filtered by search pattern
|
|
4
|
-
*/
|
|
5
|
-
export declare function listModels(modelRegistry: ModelRegistry, searchPattern?: string): Promise<void>;
|
|
6
|
-
/**
|
|
7
|
-
* Options for the `--list-models` command entry point.
|
|
8
|
-
*/
|
|
9
|
-
export interface RunListModelsOptions {
|
|
10
|
-
modelRegistry: ModelRegistry;
|
|
11
|
-
cwd: string;
|
|
12
|
-
/** CLI-supplied extension paths (e.g. from `-e <path>`). */
|
|
13
|
-
additionalExtensionPaths?: string[];
|
|
14
|
-
/** Extension paths configured under `extensions:` in user settings. */
|
|
15
|
-
settingsExtensions?: string[];
|
|
16
|
-
/** Disabled extension ids from settings (`disabledExtensions`). */
|
|
17
|
-
disabledExtensionIds?: string[];
|
|
18
|
-
/** When true, skip discovery and only load `additionalExtensionPaths`. */
|
|
19
|
-
disableExtensionDiscovery?: boolean;
|
|
20
|
-
searchPattern?: string;
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Loads extensions (CLI `-e` paths and `settings.extensions`) and surfaces
|
|
24
|
-
* any provider/model registrations on the supplied `modelRegistry` before
|
|
25
|
-
* delegating to {@link listModels}. This is the single entry point used by
|
|
26
|
-
* `--list-models` and exists to ensure extension-contributed providers are
|
|
27
|
-
* visible in the listing (issue #905). The load is intentionally narrow:
|
|
28
|
-
* no agent loop, no MCP servers, no custom-tool registration.
|
|
29
|
-
*/
|
|
30
|
-
export declare function runListModelsCommand(options: RunListModelsOptions): Promise<void>;
|