Skip to content

Feat/inject#19

Merged
JosXa merged 5 commits intoJosXa:mainfrom
spoons-and-mirrors:feat/inject
Feb 1, 2026
Merged

Feat/inject#19
JosXa merged 5 commits intoJosXa:mainfrom
spoons-and-mirrors:feat/inject

Conversation

@spoons-and-mirrors
Copy link
Copy Markdown
Contributor

@spoons-and-mirrors spoons-and-mirrors commented Jan 27, 2026

Implementing #15

to summarize, the inject flag (might need semantic change) allows to send a snippet to the model during the agentic loop cycle, from user message to session.idle.

this is meant for things like "always read files speculatively, use parallel tool calling for read and edits as much as possible, use the question tool to ask me any question you migth have" things like that really - behavioral notes that you want to always have in "front" of the model's eyes without poisoning the context because you added it 13 times in your convo, it's never persisted for more than the agent loop, until session idle.

I think of it as a system prompt extension... sortof.

instead of cramming a huge paragraph in the system prompt, you can mention the thing in the system, and rehash/go deeper in that message - without poisoning context. And of course, being in front of the model leverages recency bias in your favor, so the model doesn't "forget" to use parallel tool calling or other things you prefer.

TLDR: really great for behavioral steering

PS: maybe "ephemeral" makes more sense than inject, since everything is an injection, that name might be confusing

Comment thread src/injection-manager.ts
/**
* Adds additional injections to an existing session without duplicates.
*/
addInjections(sessionID: string, newInjections: string[]): void {
Copy link
Copy Markdown
Owner

@JosXa JosXa Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Use rest params instead of an array
    (Addressed in commit 04a085f)

Comment thread src/injection-manager.ts
* Injections persist for the entire agentic loop until session idle.
*/
export class InjectionManager {
private activeInjections = new Map<string, string[]>();
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we potentially just store the injection keys as the map values?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea for a future optimization. For now, keeping the simple Map<sessionID, string[]> approach since duplicate checking is sufficient.

Comment thread index.ts
parts: [{ type: "text", text: injectText }],
});
}
logger.debug("Injected ephemeral user messages", {
Copy link
Copy Markdown
Owner

@JosXa JosXa Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh, this was the wording that finally clued me in on what this was about. Real messages kept at the top of the conversation.
Still not sure this is in scope of "snippets" :D But we can try

@JosXa
Copy link
Copy Markdown
Owner

JosXa commented Jan 31, 2026

hey opencode, can you address the comments and put this feature behind an experimental flag? New experimental: {...} in config. Push on this branch directly, but don't change history.

@JosXa
Copy link
Copy Markdown
Owner

JosXa commented Jan 31, 2026

Changes Made

Addressed the PR feedback to put the inject feature behind an experimental flag.

Summary of Changes

  1. Added experimental.inject config option (src/config.ts)

    • New ExperimentalConfig interface with inject: boolean (default: false)
    • Updated SnippetsConfig, RawConfig, DEFAULT_CONFIG, and mergeConfig
    • Updated default config file template with documentation
  2. Changed addInjections to use rest params (src/injection-manager.ts)

    • Signature changed from addInjections(sessionID, newInjections: string[]) to addInjections(sessionID, ...newInjections: string[])
  3. Gated all inject functionality behind the flag (index.ts)

    • setInjections only called when config.experimental.inject is true
    • addInjections only called when config.experimental.inject is true
    • Transform hook only injects messages when config.experimental.inject is true
  4. Updated JSON schema (schema/config.schema.json)

    • Added experimental.inject property with documentation
  5. Updated tests (src/config.test.ts)

    • Added experimental: { inject: false } to expected default config objects

Usage

To enable inject blocks, users add to their config:

{
  "experimental": {
    "inject": true
  }
}

All 186 tests pass.

@JosXa JosXa mentioned this pull request Jan 31, 2026
Comment thread src/hook-types.ts
@@ -0,0 +1,46 @@
/**
Copy link
Copy Markdown
Owner

@JosXa JosXa Jan 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Oh these should definitely come from the OpenCode SDK, check node_modules for the correct import paths!
    (Addressed - types imported from @opencode-ai/sdk at src/hook-types.ts:5)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - types now imported from @opencode-ai/sdk (not plugin, as plugin only imports them internally but doesn't re-export).

@JosXa
Copy link
Copy Markdown
Owner

JosXa commented Jan 31, 2026

  • <inject> must not be mixed with <prepend> or <append>. Create tests to ensure this!

✅ Validation added in src/expander.ts:102-106
✅ 3 tests added in src/expander.test.ts:601-626

@JosXa
Copy link
Copy Markdown
Owner

JosXa commented Jan 31, 2026

Hey OpenCode, two more open tasks for you :)

@JosXa
Copy link
Copy Markdown
Owner

JosXa commented Jan 31, 2026

Changes Made

Addressed the two open tasks:

1. Validation: <inject> cannot be mixed with <prepend> or <append>

Added runtime validation in src/expander.ts:parseSnippetBlocks() that returns null (with a warning log) when a snippet contains <inject> alongside <prepend> or <append> blocks.

2. Tests for the validation

Added 3 new tests in src/expander.test.ts:

  • <inject> mixed with <prepend> → returns null
  • <inject> mixed with <append> → returns null
  • <inject> mixed with both <prepend> and <append> → returns null

3. Import types from SDK

Updated src/hook-types.ts to import Part, Message, UserMessage from @opencode-ai/sdk instead of defining them locally.

Note: Types come from @opencode-ai/sdk (not @opencode-ai/plugin) because the plugin package imports them internally but doesn't re-export them.


All 189 tests pass ✅

@JosXa JosXa merged commit 161d2c6 into JosXa:main Feb 1, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants