[PR #3433] feat: add noReply parameter for silent message insertion #10643

Closed
opened 2026-02-16 18:15:21 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/anomalyco/opencode/pull/3433

State: closed
Merged: Yes


PR Description: Silent Message Insertion (noReply Parameter)

Problem Statement

What pain point in the dev workflow does this address?

Plugin developers cannot inject context into sessions without triggering unnecessary AI inference and consuming API tokens. The current POST /session/{id}/message endpoint always triggers AI inference when creating a user message, making it impossible to replicate Anthropic's Agent Skills pattern where context is injected as user messages that persist in session history without generating AI responses.

What was the previous state?

  • No way to add context to a session without receiving an AssistantMessage response
  • Plugins had to use tool responses for context injection (which may be purged from context)
  • Wasted tokens and API costs on AI responses that serve no purpose
  • Every message insertion took 5-30 seconds due to inference, even when only adding context

Why is this change needed now?

  1. Enable efficient plugin development (context injection without token waste)
  2. Support persistent context that won't be auto-purged like tool responses
  3. Save significant API costs (zero tokens for context injection)

Technical Changes

High-level approach:

Minimal "surgical insertion" - add a single optional boolean parameter (noReply) to the existing prompt schema and implement early return logic to bypass AI inference when set to true. The implementation leverages existing type compatibility (UserMessages already satisfy MessageV2.WithParts), requiring only 6 lines of core code changes.

Key components modified/added:

  • packages/opencode/src/session/prompt.ts

    • Added noReply: z.boolean().optional() to PromptInput schema (line 97)
    • Added early return logic after user message creation (lines 156-159)
    • When noReply: true, returns userMsg immediately, bypassing queue check, model resolution, system prompt, tool resolution, and streaming
  • packages/opencode/test/session/prompt.test.ts

    • 8 comprehensive tests covering all noReply scenarios
    • Tests default behavior, explicit noReply: false, context injection, persistence, performance
    • All tests passing with proper type narrowing for discriminated unions
  • packages/sdk/js/src/gen/types.gen.ts (AUTO-REGENERATED)

    • Added noReply?: boolean to SessionPromptData type
    • SDK automatically updated via OpenAPI spec generation
  • packages/web/src/content/docs/server.mdx

    • Updated POST /session/:id/message endpoint documentation
    • Notes that noReply: true skips AI inference and returns UserMessage
  • packages/web/src/content/docs/sdk.mdx

    • Updated session.prompt() method documentation with example
    • Added context injection example using noReply: true

Design decisions and rationale:

  1. Parameter Name: noReply

    • Explicit: "Don't reply to this message" - crystal clear intent
    • Simple: Boolean flag, not complex discriminated union
    • User-facing: Describes outcome, not implementation details
    • Distinct: Avoids overloaded terms like "mode"
    • Rejected alternatives: mode: "context" (overloaded), skipInference (too technical), contextOnly (less clear)
  2. Return Type: Keep MessageV2.WithParts

    • User and Assistant messages both satisfy this type via discriminated union
    • Consumers check info.role to determine actual type
    • Avoids complex function overloads
    • No signature change needed
  3. Early Return Placement

    • AFTER user message creation and persistence (line 152)
    • AFTER session timestamp update (line 153)
    • BEFORE queue check (line 155) - no need to queue if not inferencing
    • Skips all downstream logic: model resolution, system prompt, processor creation, tool resolution, streaming, assistant message creation
  4. Test Strategy

    • Comprehensive coverage of both code paths
    • Type narrowing for discriminated unions to satisfy TypeScript
    • Performance benchmarks included (< 1s for noReply: true)

Alternatives considered:

  1. Separate endpoint (e.g., POST /session/:id/context)

    • Rejected: Creates API fragmentation, more complex to discover
    • Optional parameter on existing endpoint is cleaner
  2. Different parameter names (see "Design decisions" above)

    • Evaluated mode, skipInference, contextOnly, silent
    • Selected noReply for clarity and explicitness
  3. Special message type

    • Rejected: UserMessages already work perfectly for this use case
    • No need to create new types

Dependencies added/updated:

  • None - uses existing infrastructure

Impact

For Developers

New commands/workflows available:

// NEW: Inject context without triggering AI response
const contextMsg = await client.session.prompt({
  path: { id: sessionId },
  body: {
    noReply: true,
    parts: [{ type: "text", text: "You are a helpful coding assistant." }],
  },
})
// Returns UserMessage in ~5ms, zero tokens consumed

// Subsequent prompts include the context
const response = await client.session.prompt({
  path: { id: sessionId },
  body: {
    parts: [{ type: "text", text: "How do I use TypeScript generics?" }],
  },
})
// AI response will be informed by the context message above

Changed behavior:

  • No changes to existing behavior - noReply parameter is optional and defaults to false
  • Existing code continues to work unchanged

Breaking changes:

  • No breaking changes

For AI Agents

Can agents discover and use the new functionality?

  • Documented in API documentation (server.mdx, sdk.mdx)
  • Commands are self-documenting (TypeScript types include parameter)

How does this enhance agent capabilities?

Agents and plugins can now:

  1. Inject persistent context without triggering inference or consuming tokens
  2. Save API costs - zero tokens for context injection vs hundreds previously
  3. Control context lifetime - user messages persist in history, unlike tool responses that may be purged

Verification

Automated Tests

  • Unit tests pass: bun test packages/opencode/test/session/prompt.test.ts (8/8 passing)
  • Integration tests pass: All 8 tests cover integration scenarios (persistence, context usage)
  • Linting/type checking passes: bun run typecheck (zero errors)
  • CI pipeline passes: Pre-push hook verified all packages typecheck

Manual Testing

  • Tested core functionality manually
  • Tested edge cases (multiple sequential messages, mixed with inference)
  • Tested error handling (implicit through test suite)

Manual testing steps performed:

  1. Created session and sent message with noReply: true

    • Verified UserMessage returned with role "user"
    • Verified completion in < 10ms (vs 8-11s for inference)
    • Verified message persisted in session history
  2. Sent multiple sequential noReply: true messages

    • Verified all persisted as user messages
    • Verified no AI responses generated
    • Verified session not locked/blocked
  3. Sent message with noReply: false after context injection

    • Verified AssistantMessage returned
    • Verified AI response included context from earlier messages
    • Verified complete message history (context + question + answer)

Related PRs/Issues:

  • Issue: #3378 - Original feature request

Backward compatibility notes:

Fully backward compatible:

  • noReply parameter is optional (defaults to false)
  • All existing API calls continue working unchanged
  • No database migrations required
  • No changes to type signatures (returns same MessageV2.WithParts union)

Changelog Entry

Type: feat

Summary (1-2 lines):

Add optional noReply parameter to POST /session/{id}/message endpoint for context injection without AI inference, enabling the Anthropic Agent Skills pattern with zero token consumption and 1000x performance improvement.

Full entry:

- feat: add noReply parameter for silent message insertion
  - Add optional noReply: boolean parameter to POST /session/{id}/message
  - When noReply: true, creates UserMessage without triggering AI inference
  - Cost: Zero tokens consumed for context injection
  - Fully backward compatible (optional parameter, defaults to false)
  - Includes comprehensive test suite (8 tests, all passing)
  - SDK automatically updated with new parameter type

Screenshots/Demos

Terminal Output - Test Results

bun test v1.3.1

test/session/prompt.test.ts:
(pass) SessionPrompt.prompt > default behavior (noReply undefined) triggers inference [11848.38ms]
(pass) SessionPrompt.prompt > noReply: false explicitly triggers inference [10091.07ms]
(pass) SessionPrompt.prompt > noReply: true skips inference and returns user message [4.61ms]
(pass) SessionPrompt.prompt > noReply: true with multiple parts [4.71ms]
(pass) SessionPrompt.prompt > noReply: true messages persist in session history [3.32ms]
(pass) SessionPrompt.prompt > multiple noReply: true messages in sequence [3.71ms]
(pass) SessionPrompt.prompt > noReply: true then noReply: false uses context [10809.59ms]
(pass) SessionPrompt.prompt > noReply: true does not block session for other requests [4.16ms]

 8 pass
 0 fail
 30 expect() calls
Ran 8 tests across 1 file. [33.04s]

Performance Comparison

Operation                          Time        Tokens
─────────────────────────────────────────────────────
noReply: true (context only)       ~5ms        0
noReply: false (with inference)    8-11s       500+

Speedup: ~1000x faster
Cost savings: 100% for context injection

Example Usage - TypeScript SDK

// Plugin skill injection example
async function loadSkill(skillName: string, sessionID: string) {
  const client = createOpencodeClient()

  // Inject skill instructions as user messages (no AI response)
  await client.session.prompt({
    path: { id: sessionID },
    body: {
      noReply: true,
      parts: [
        { type: "text", text: `The '${skillName}' skill is now active.` },
        { type: "text", text: `Base directory: /path/to/skill\n\n${skillInstructions}` },
      ],
    },
  })

  // Return minimal tool response
  return `✓ Skill '${skillName}' loaded successfully`
}

Additional Notes

Performance considerations:

  • Speed: noReply: true completes in ~5ms (vs 8-11 seconds for inference)
  • Token savings: 100% reduction in tokens for context injection
  • No session locking: Context messages don't block other requests

Security considerations:

  • No security implications - uses existing message creation and persistence
  • Same validation and sanitization as normal user messages
**Original Pull Request:** https://github.com/anomalyco/opencode/pull/3433 **State:** closed **Merged:** Yes --- # PR Description: Silent Message Insertion (noReply Parameter) ## Problem Statement **What pain point in the dev workflow does this address?** Plugin developers cannot inject context into sessions without triggering unnecessary AI inference and consuming API tokens. The current `POST /session/{id}/message` endpoint always triggers AI inference when creating a user message, making it impossible to replicate Anthropic's Agent Skills pattern where context is injected as user messages that persist in session history without generating AI responses. **What was the previous state?** - No way to add context to a session without receiving an AssistantMessage response - Plugins had to use tool responses for context injection (which may be purged from context) - Wasted tokens and API costs on AI responses that serve no purpose - Every message insertion took 5-30 seconds due to inference, even when only adding context **Why is this change needed now?** 1. Enable efficient plugin development (context injection without token waste) 2. Support persistent context that won't be auto-purged like tool responses 3. Save significant API costs (zero tokens for context injection) --- ## Technical Changes **High-level approach:** Minimal "surgical insertion" - add a single optional boolean parameter (`noReply`) to the existing prompt schema and implement early return logic to bypass AI inference when set to `true`. The implementation leverages existing type compatibility (UserMessages already satisfy `MessageV2.WithParts`), requiring only 6 lines of core code changes. **Key components modified/added:** - **packages/opencode/src/session/prompt.ts** - Added `noReply: z.boolean().optional()` to `PromptInput` schema (line 97) - Added early return logic after user message creation (lines 156-159) - When `noReply: true`, returns `userMsg` immediately, bypassing queue check, model resolution, system prompt, tool resolution, and streaming - **packages/opencode/test/session/prompt.test.ts** - 8 comprehensive tests covering all `noReply` scenarios - Tests default behavior, explicit `noReply: false`, context injection, persistence, performance - All tests passing with proper type narrowing for discriminated unions - **packages/sdk/js/src/gen/types.gen.ts** (AUTO-REGENERATED) - Added `noReply?: boolean` to `SessionPromptData` type - SDK automatically updated via OpenAPI spec generation - **packages/web/src/content/docs/server.mdx** - Updated POST `/session/:id/message` endpoint documentation - Notes that `noReply: true` skips AI inference and returns UserMessage - **packages/web/src/content/docs/sdk.mdx** - Updated `session.prompt()` method documentation with example - Added context injection example using `noReply: true` **Design decisions and rationale:** 1. **Parameter Name: `noReply`** - Explicit: "Don't reply to this message" - crystal clear intent - Simple: Boolean flag, not complex discriminated union - User-facing: Describes outcome, not implementation details - Distinct: Avoids overloaded terms like "mode" - Rejected alternatives: `mode: "context"` (overloaded), `skipInference` (too technical), `contextOnly` (less clear) 2. **Return Type: Keep `MessageV2.WithParts`** - User and Assistant messages both satisfy this type via discriminated union - Consumers check `info.role` to determine actual type - Avoids complex function overloads - No signature change needed 3. **Early Return Placement** - AFTER user message creation and persistence (line 152) - AFTER session timestamp update (line 153) - BEFORE queue check (line 155) - no need to queue if not inferencing - Skips all downstream logic: model resolution, system prompt, processor creation, tool resolution, streaming, assistant message creation 4. **Test Strategy** - Comprehensive coverage of both code paths - Type narrowing for discriminated unions to satisfy TypeScript - Performance benchmarks included (< 1s for noReply: true) **Alternatives considered:** 1. **Separate endpoint** (e.g., `POST /session/:id/context`) - Rejected: Creates API fragmentation, more complex to discover - Optional parameter on existing endpoint is cleaner 2. **Different parameter names** (see "Design decisions" above) - Evaluated `mode`, `skipInference`, `contextOnly`, `silent` - Selected `noReply` for clarity and explicitness 3. **Special message type** - Rejected: UserMessages already work perfectly for this use case - No need to create new types **Dependencies added/updated:** - None - uses existing infrastructure --- ## Impact ### For Developers **New commands/workflows available:** ```typescript // NEW: Inject context without triggering AI response const contextMsg = await client.session.prompt({ path: { id: sessionId }, body: { noReply: true, parts: [{ type: "text", text: "You are a helpful coding assistant." }], }, }) // Returns UserMessage in ~5ms, zero tokens consumed // Subsequent prompts include the context const response = await client.session.prompt({ path: { id: sessionId }, body: { parts: [{ type: "text", text: "How do I use TypeScript generics?" }], }, }) // AI response will be informed by the context message above ``` **Changed behavior:** - No changes to existing behavior - `noReply` parameter is optional and defaults to `false` - Existing code continues to work unchanged **Breaking changes:** - [x] No breaking changes ### For AI Agents **Can agents discover and use the new functionality?** - [x] Documented in API documentation (server.mdx, sdk.mdx) - [x] Commands are self-documenting (TypeScript types include parameter) **How does this enhance agent capabilities?** Agents and plugins can now: 1. **Inject persistent context** without triggering inference or consuming tokens 2. **Save API costs** - zero tokens for context injection vs hundreds previously 3. **Control context lifetime** - user messages persist in history, unlike tool responses that may be purged --- ## Verification ### Automated Tests - [x] Unit tests pass: `bun test packages/opencode/test/session/prompt.test.ts` (8/8 passing) - [x] Integration tests pass: All 8 tests cover integration scenarios (persistence, context usage) - [x] Linting/type checking passes: `bun run typecheck` (zero errors) - [x] CI pipeline passes: Pre-push hook verified all packages typecheck ### Manual Testing - [x] Tested core functionality manually - [x] Tested edge cases (multiple sequential messages, mixed with inference) - [x] Tested error handling (implicit through test suite) **Manual testing steps performed:** 1. Created session and sent message with `noReply: true` - Verified UserMessage returned with role "user" - Verified completion in < 10ms (vs 8-11s for inference) - Verified message persisted in session history 2. Sent multiple sequential `noReply: true` messages - Verified all persisted as user messages - Verified no AI responses generated - Verified session not locked/blocked 3. Sent message with `noReply: false` after context injection - Verified AssistantMessage returned - Verified AI response included context from earlier messages - Verified complete message history (context + question + answer) --- **Related PRs/Issues:** - Issue: [#3378](https://github.com/sst/opencode/issues/3378) - Original feature request --- **Backward compatibility notes:** Fully backward compatible: - `noReply` parameter is optional (defaults to `false`) - All existing API calls continue working unchanged - No database migrations required - No changes to type signatures (returns same `MessageV2.WithParts` union) --- ## Changelog Entry **Type:** feat **Summary (1-2 lines):** Add optional `noReply` parameter to POST /session/{id}/message endpoint for context injection without AI inference, enabling the Anthropic Agent Skills pattern with zero token consumption and 1000x performance improvement. **Full entry:** ``` - feat: add noReply parameter for silent message insertion - Add optional noReply: boolean parameter to POST /session/{id}/message - When noReply: true, creates UserMessage without triggering AI inference - Cost: Zero tokens consumed for context injection - Fully backward compatible (optional parameter, defaults to false) - Includes comprehensive test suite (8 tests, all passing) - SDK automatically updated with new parameter type ``` --- ## Screenshots/Demos ### Terminal Output - Test Results ``` bun test v1.3.1 test/session/prompt.test.ts: (pass) SessionPrompt.prompt > default behavior (noReply undefined) triggers inference [11848.38ms] (pass) SessionPrompt.prompt > noReply: false explicitly triggers inference [10091.07ms] (pass) SessionPrompt.prompt > noReply: true skips inference and returns user message [4.61ms] (pass) SessionPrompt.prompt > noReply: true with multiple parts [4.71ms] (pass) SessionPrompt.prompt > noReply: true messages persist in session history [3.32ms] (pass) SessionPrompt.prompt > multiple noReply: true messages in sequence [3.71ms] (pass) SessionPrompt.prompt > noReply: true then noReply: false uses context [10809.59ms] (pass) SessionPrompt.prompt > noReply: true does not block session for other requests [4.16ms] 8 pass 0 fail 30 expect() calls Ran 8 tests across 1 file. [33.04s] ``` ### Performance Comparison ``` Operation Time Tokens ───────────────────────────────────────────────────── noReply: true (context only) ~5ms 0 noReply: false (with inference) 8-11s 500+ Speedup: ~1000x faster Cost savings: 100% for context injection ``` ### Example Usage - TypeScript SDK ```typescript // Plugin skill injection example async function loadSkill(skillName: string, sessionID: string) { const client = createOpencodeClient() // Inject skill instructions as user messages (no AI response) await client.session.prompt({ path: { id: sessionID }, body: { noReply: true, parts: [ { type: "text", text: `The '${skillName}' skill is now active.` }, { type: "text", text: `Base directory: /path/to/skill\n\n${skillInstructions}` }, ], }, }) // Return minimal tool response return `✓ Skill '${skillName}' loaded successfully` } ``` --- ## Additional Notes **Performance considerations:** - **Speed**: `noReply: true` completes in ~5ms (vs 8-11 seconds for inference) - **Token savings**: 100% reduction in tokens for context injection - **No session locking**: Context messages don't block other requests **Security considerations:** - No security implications - uses existing message creation and persistence - Same validation and sanitization as normal user messages
yindo added the pull-request label 2026-02-16 18:15:21 -05:00
yindo closed this issue 2026-02-16 18:15:21 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#10643