[FEATURE]: Silent Message Insertion API #2244

Closed
opened 2026-02-16 17:34:48 -05:00 by yindo · 12 comments
Owner

Originally created by @malhashemi on GitHub (Oct 23, 2025).

Originally assigned to: @malhashemi on GitHub.

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

Summary

Add ability to insert user messages into sessions without triggering AI inference. This enables plugins to inject context (like Anthropic's Agent Skills pattern) without generating AI responses.

Problem Statement

Current Limitation: The POST /session/{id}/message endpoint is architecturally coupled - it always triggers AI inference when creating a user message. There is no way to add context to a session without receiving an AssistantMessage response.

Impact on Plugins:

  • Cannot replicate Anthropic's Agent Skills pattern
  • Must use tool responses for context injection
  • Tool responses may be purged from context automatically
  • Unnecessary AI responses when only adding context

Current Workaround (from opencode-skills plugin):

async execute(args, toolCtx) {
  // Can only return string as tool response
  return `# ⚠️ SKILL EXECUTION INSTRUCTIONS ⚠️

**SKILL NAME:** ${skill.name}
**SKILL DIRECTORY:** ${skill.fullPath}/

## PATH RESOLUTION RULES:
All file paths mentioned below are relative to: ${skill.fullPath}/

---

${skill.content}
`
}

Problems with Current Approach:

  • Tool responses may be purged from context
  • More verbose than needed
  • Doesn't match Anthropic's clean pattern
  • No control over message persistence

Use Case: Anthropic Agent Skills Pattern

Anthropic's Claude Code uses a clean pattern for skill execution:

1. Tool call to skills_klorpify
2. User message: "The 'klorpify' skill is running"
3. User message: "Base directory: /path\n\nContent..."
4. Tool response: "Launching skill: klorpify"

Key Characteristics:

  • User messages inserted without triggering AI
  • Multiple user messages in sequence
  • Minimal tool response
  • Clean, persistent context injection

This pattern is currently impossible in OpenCode because:

  1. session.prompt() always triggers inference
  2. No API exists for silent message insertion
  3. Plugins can only return strings from tools
  4. No internal API access from plugin context

Proposed Solution

Add a skipInference parameter to the existing POST /session/{id}/message endpoint.

Option A: skipInference Parameter

API Change:

POST /session/{id}/message
{
  "skipInference": true,  // ← NEW optional parameter
  "parts": [{
    "type": "text",
    "text": "Context to inject without AI response"
  }]
}

Behavior When skipInference: true:

  • Creates and stores UserMessage
  • Publishes message.updated event
  • Does NOT call streamText()
  • Returns { message: UserMessage } (no AssistantMessage)

Plugin Usage Example:

async execute(args, toolCtx) {
  const client = createOpencodeClient()

  // Inject skill content as user message
  await client.session.prompt({
    path: { id: toolCtx.sessionID },
    body: {
      skipInference: true,
      parts: [{
        type: "text",
        text: `Base directory: ${skill.fullPath}\n\n${skill.content}`,
      }]
    }
  })

  // Return minimal tool response
  return `Launching skill: ${skill.name}`
}

Advantages:

  • Minimal code changes
  • Backward compatible (default: false)
  • Explicit and clear intent
  • Works with existing SDK clients
  • No new endpoint needed

Option B: New Endpoint (Alternative)

Create dedicated POST /session/{id}/context endpoint for context injection.

Advantages:

  • Cleaner separation of concerns
  • Explicit intent via endpoint name
  • Doesn't modify existing endpoint semantics

Disadvantages:

  • More code changes
  • New API surface to maintain
  • Requires SDK regeneration

Related Issues

Questions for Discussion

  1. How should injected messages be rendered in the UI (hidden, visible)?

I'm happy to implement this feature if the approach is approved. Please let me know if you'd like me to open a PR with Option A (recommended) or if you'd prefer Option B (new endpoint).

Originally created by @malhashemi on GitHub (Oct 23, 2025). Originally assigned to: @malhashemi on GitHub. ### Feature hasn't been suggested before. - [x] I have verified this feature I'm about to request hasn't been suggested before. ### Describe the enhancement you want to request ## Summary Add ability to insert user messages into sessions without triggering AI inference. This enables plugins to inject context (like Anthropic's Agent Skills pattern) without generating AI responses. ## Problem Statement **Current Limitation**: The `POST /session/{id}/message` endpoint is architecturally coupled - it **always** triggers AI inference when creating a user message. There is no way to add context to a session without receiving an AssistantMessage response. **Impact on Plugins**: - Cannot replicate Anthropic's Agent Skills pattern - Must use tool responses for context injection - Tool responses may be purged from context automatically - Unnecessary AI responses when only adding context **Current Workaround** (from opencode-skills plugin): ```typescript async execute(args, toolCtx) { // Can only return string as tool response return `# ⚠️ SKILL EXECUTION INSTRUCTIONS ⚠️ **SKILL NAME:** ${skill.name} **SKILL DIRECTORY:** ${skill.fullPath}/ ## PATH RESOLUTION RULES: All file paths mentioned below are relative to: ${skill.fullPath}/ --- ${skill.content} ` } ``` **Problems with Current Approach**: - ❌ Tool responses may be purged from context - ❌ More verbose than needed - ❌ Doesn't match Anthropic's clean pattern - ❌ No control over message persistence ## Use Case: Anthropic Agent Skills Pattern Anthropic's Claude Code uses a clean pattern for skill execution: ``` 1. Tool call to skills_klorpify 2. User message: "The 'klorpify' skill is running" 3. User message: "Base directory: /path\n\nContent..." 4. Tool response: "Launching skill: klorpify" ``` **Key Characteristics**: - User messages inserted **without triggering AI** - Multiple user messages in sequence - Minimal tool response - Clean, persistent context injection **This pattern is currently impossible in OpenCode** because: 1. `session.prompt()` always triggers inference 2. No API exists for silent message insertion 3. Plugins can only return strings from tools 4. No internal API access from plugin context ## Proposed Solution Add a `skipInference` parameter to the existing `POST /session/{id}/message` endpoint. ### Option A: `skipInference` Parameter **API Change**: ```typescript POST /session/{id}/message { "skipInference": true, // ← NEW optional parameter "parts": [{ "type": "text", "text": "Context to inject without AI response" }] } ``` **Behavior When `skipInference: true`**: - Creates and stores UserMessage - Publishes `message.updated` event - **Does NOT call `streamText()`** - Returns `{ message: UserMessage }` (no AssistantMessage) **Plugin Usage Example**: ```typescript async execute(args, toolCtx) { const client = createOpencodeClient() // Inject skill content as user message await client.session.prompt({ path: { id: toolCtx.sessionID }, body: { skipInference: true, parts: [{ type: "text", text: `Base directory: ${skill.fullPath}\n\n${skill.content}`, }] } }) // Return minimal tool response return `Launching skill: ${skill.name}` } ``` **Advantages**: - ✅ Minimal code changes - ✅ Backward compatible (default: false) - ✅ Explicit and clear intent - ✅ Works with existing SDK clients - ✅ No new endpoint needed ### Option B: New Endpoint (Alternative) Create dedicated `POST /session/{id}/context` endpoint for context injection. **Advantages**: - ✅ Cleaner separation of concerns - ✅ Explicit intent via endpoint name - ✅ Doesn't modify existing endpoint semantics **Disadvantages**: - ❌ More code changes - ❌ New API surface to maintain - ❌ Requires SDK regeneration ## Related Issues - [opencode-skills Issue #2](https://github.com/malhashemi/opencode-skills/issues/2) - Anthropic Agent Skills pattern compatibility ## Questions for Discussion 1. How should injected messages be rendered in the UI (hidden, visible)? --- **I'm happy to implement this feature if the approach is approved.** Please let me know if you'd like me to open a PR with Option A (recommended) or if you'd prefer Option B (new endpoint).
yindo added the discussion label 2026-02-16 17:34:48 -05:00
yindo closed this issue 2026-02-16 17:34:48 -05:00
Author
Owner

@rekram1-node commented on GitHub (Oct 23, 2025):

how does this solve for tool call purges tho, thats a good point for this

@rekram1-node commented on GitHub (Oct 23, 2025): how does this solve for tool call purges tho, thats a good point for this
Author
Owner

@malhashemi commented on GitHub (Oct 23, 2025):

It would allow plugins to insert a message into context (in my case it is the SKILL.md content) as a user message (Exactly how anthropic is doing it at the moment) currently I am returning the file content as a tool response which could get purged.

If I simply use the current endpoint then it will trigger a new AI response. If the agent is already working it will be queued.

I know the team is busy and I think I have an idea of a minimal approach to allow this behaviour but would like to get your blessings before pulling any work/PR.

@malhashemi commented on GitHub (Oct 23, 2025): It would allow plugins to insert a message into context (in my case it is the SKILL.md content) as a user message (Exactly how anthropic is doing it at the moment) currently I am returning the file content as a tool response which could get purged. If I simply use the current endpoint then it will trigger a new AI response. If the agent is already working it will be queued. I know the team is busy and I think I have an idea of a minimal approach to allow this behaviour but would like to get your blessings before pulling any work/PR.
Author
Owner

@rekram1-node commented on GitHub (Oct 24, 2025):

ah okay i see

@rekram1-node commented on GitHub (Oct 24, 2025): ah okay i see
Author
Owner

@rekram1-node commented on GitHub (Oct 24, 2025):

Yeah i think that's fine then you just need to add it to prompt input, add early return after create User message call, and then regenerate typescript sdk

I don't love skipInference but other names don't come to mind

@rekram1-node commented on GitHub (Oct 24, 2025): Yeah i think that's fine then you just need to add it to prompt input, add early return after create User message call, and then regenerate typescript sdk I don't love `skipInference` but other names don't come to mind
Author
Owner

@gregid commented on GitHub (Oct 24, 2025):

Nice addition.
Some alternative names that come to mind:

"silent": true,
"background": true,
"mode": "context" //with "message" or "prompt" being the default
@gregid commented on GitHub (Oct 24, 2025): Nice addition. Some alternative names that come to mind: ```js "silent": true, "background": true, "mode": "context" //with "message" or "prompt" being the default ```
Author
Owner

@malhashemi commented on GitHub (Oct 25, 2025):

@gregid thanks for your comment I think I will go with "mode": "context" with the default being "message", just in case someone might think of an addtional functionality in the future so they can simply expand with another mode. @rekram1-node what do you think?

@malhashemi commented on GitHub (Oct 25, 2025): @gregid thanks for your comment I think I will go with `"mode": "context"` with the default being "message", just in case someone might think of an addtional functionality in the future so they can simply expand with another mode. @rekram1-node what do you think?
Author
Owner

@rekram1-node commented on GitHub (Oct 25, 2025):

@malhashemi we already have other notions of "mode" and so do other tools, I'd prefer something more explicit and distinct

@rekram1-node commented on GitHub (Oct 25, 2025): @malhashemi we already have other notions of "mode" and so do other tools, I'd prefer something more explicit and distinct
Author
Owner

@malhashemi commented on GitHub (Oct 25, 2025):

@rekram1-node the only other option I can think of other than skipInference: true which explains the functionality technically would be contextOnly: true that one is focusing on the behaviour rather than what it technically does. Pick one :)

@malhashemi commented on GitHub (Oct 25, 2025): @rekram1-node the only other option I can think of other than `skipInference: true` which explains the functionality technically would be `contextOnly: true` that one is focusing on the behaviour rather than what it technically does. Pick one :)
Author
Owner

@rekram1-node commented on GitHub (Oct 25, 2025):

I think either of those work fine I will leave it up to you, I will say that I do kinda like noReply it is pretty explicit and simple (but like i said u can decide :))

@rekram1-node commented on GitHub (Oct 25, 2025): I think either of those work fine I will leave it up to you, I will say that I do kinda like `noReply` it is pretty explicit and simple (but like i said u can decide :))
Author
Owner

@malhashemi commented on GitHub (Oct 25, 2025):

noReply is actually nice I will go with that one! Thanks

@malhashemi commented on GitHub (Oct 25, 2025): `noReply` is actually nice I will go with that one! Thanks
Author
Owner

@malhashemi commented on GitHub (Oct 25, 2025):

@rekram1-node ready for your review, I have added tests as well let me know if you would like to drop these, feel free to edit as you see fit. Thanks

@malhashemi commented on GitHub (Oct 25, 2025): @rekram1-node ready for your review, I have added tests as well let me know if you would like to drop these, feel free to edit as you see fit. Thanks
Author
Owner

@malhashemi commented on GitHub (Oct 26, 2025):

This is now resolved by #3433

@malhashemi commented on GitHub (Oct 26, 2025): This is now resolved by #3433
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#2244