Skip to content

Add AI conversation memory and cost tracking protocols#88

Merged
hotlong merged 2 commits intomainfrom
copilot/add-ai-conversation-memory
Jan 23, 2026
Merged

Add AI conversation memory and cost tracking protocols#88
hotlong merged 2 commits intomainfrom
copilot/add-ai-conversation-memory

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Jan 23, 2026

Implements two core AI protocols: conversation state management with token budgeting, and comprehensive cost tracking with multi-level budget enforcement.

Conversation Protocol (conversation.zod.ts)

Multi-turn conversation state with token-aware context management:

  • Message schema supporting multimodal content (text, image, file) and tool calls
  • Token budget strategies: FIFO, importance-based, semantic similarity, sliding window, summarization
  • Automatic context pruning with configurable reserves and buffers
  • Session lifecycle with context preservation and analytics
const session: ConversationSession = {
  id: 'session-1',
  context: { sessionId: 'session-1', userId: 'user-1', agentId: 'support_agent' },
  tokenBudget: {
    maxTokens: 8192,
    strategy: 'sliding_window',
    slidingWindowSize: 20,
    enableSummarization: true,
  },
  messages: [...],
  tokens: { totalTokens: 230, budgetRemaining: 7962, budgetPercentage: 0.028 },
};

Cost Protocol (cost.zod.ts)

Granular cost tracking with hierarchical budget enforcement:

  • Cost entry tracking by model, provider, operation, user, agent, object
  • Budget types: global, user, agent, object, project, department
  • Alert system with threshold warnings and anomaly detection
  • Cost analytics with multi-dimensional breakdowns
  • Optimization recommendations based on usage patterns
const budget: BudgetLimit = {
  type: 'user',
  scope: 'user-123',
  maxCost: 100,
  period: 'monthly',
  softLimit: 80,
  warnThresholds: [0.5, 0.75, 0.9],
  enforced: true,
  allowRollover: true,
};

Both protocols export TypeScript types derived from Zod schemas for runtime validation and type safety.

Original prompt
  1. AI 对话记忆

文件: packages/spec/src/ai/conversation.zod.ts
工作量: 3 天
功能: 多轮 AI 对话,令牌预算管理

  1. AI 成本跟踪

文件: packages/spec/src/ai/cost.zod.ts
工作量: 3 天
功能: 监控和控制 AI API 成本


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

@vercel
Copy link
Copy Markdown

vercel bot commented Jan 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
spec Ready Ready Preview, Comment Jan 23, 2026 0:00am

Request Review

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Add multi-turn AI conversation memory with token management Add AI conversation memory and cost tracking protocols Jan 23, 2026
Copilot AI requested a review from hotlong January 23, 2026 12:02
@hotlong hotlong marked this pull request as ready for review January 23, 2026 14:50
Copilot AI review requested due to automatic review settings January 23, 2026 14:50
@github-actions
Copy link
Copy Markdown
Contributor

This PR is very large. Consider breaking it into smaller PRs for easier review.

@hotlong hotlong merged commit 051a46c into main Jan 23, 2026
14 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds new AI protocol schemas for (1) multi-turn conversation memory with token budgeting and (2) detailed cost tracking with budget/alert/reporting structures, and wires them into the AI module exports with accompanying Vitest coverage.

Changes:

  • Introduces Conversation* Zod schemas/types for message/session state, token budgeting, pruning events, and analytics.
  • Introduces Cost* Zod schemas/types for cost entries, budgets/status, alerts, analytics, and query filters.
  • Adds Vitest test suites for both protocols and exports them from packages/spec/src/ai/index.ts (plus new npm lockfiles).

Reviewed changes

Copilot reviewed 5 out of 7 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
packages/spec/src/ai/index.ts Exposes the new conversation and cost protocols from the AI namespace.
packages/spec/src/ai/conversation.zod.ts Defines conversation message/session/token budget schemas and derived types.
packages/spec/src/ai/conversation.test.ts Adds Vitest coverage validating conversation protocol parsing and defaults.
packages/spec/src/ai/cost.zod.ts Defines cost tracking/budget/alert/report schemas and derived types.
packages/spec/src/ai/cost.test.ts Adds Vitest coverage validating cost protocol parsing and defaults.
packages/spec/package-lock.json Adds an npm lockfile for @objectstack/spec.
package-lock.json Adds an npm lockfile at monorepo root.
Files not reviewed (1)
  • packages/spec/package-lock.json: Language not supported

Comment on lines +1 to +12
{
"name": "@objectstack/spec",
"version": "0.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@objectstack/spec",
"version": "0.3.0",
"license": "Apache-2.0",
"dependencies": {
"zod": "^3.22.4"
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

Since the monorepo uses pnpm (root package.json packageManager + pnpm-lock.yaml), committing an additional packages/spec/package-lock.json adds a second lockfile for the same package set and can lead to inconsistent installs. Consider removing this lockfile and relying on pnpm’s lockfile only.

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +43
*/
export const MessageContentSchema = z.object({
type: MessageContentTypeSchema.default('text'),
text: z.string().optional().describe('Text content'),
imageUrl: z.string().url().optional().describe('Image URL for vision models'),
fileUrl: z.string().url().optional().describe('File attachment URL'),
mimeType: z.string().optional().describe('MIME type for files'),
metadata: z.record(z.any()).optional().describe('Additional metadata'),
});

Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

MessageContentSchema is too permissive: it allows invalid combinations like { type: "image" } without imageUrl, or imageUrl/fileUrl present while type defaults to text. Consider modeling content as a discriminated union on type (e.g., text requires text, image requires imageUrl, file requires fileUrl, etc.) so invalid message payloads are rejected at validation time.

Suggested change
*/
export const MessageContentSchema = z.object({
type: MessageContentTypeSchema.default('text'),
text: z.string().optional().describe('Text content'),
imageUrl: z.string().url().optional().describe('Image URL for vision models'),
fileUrl: z.string().url().optional().describe('File attachment URL'),
mimeType: z.string().optional().describe('MIME type for files'),
metadata: z.record(z.any()).optional().describe('Additional metadata'),
});
*
* Discriminated union on `type` to ensure valid combinations:
* - `text` requires `text`
* - `image` requires `imageUrl`
* - `file` requires `fileUrl`
* - `code` requires `text` (code content)
* - `structured` primarily uses `metadata` / optional `text`
*/
const BaseMessageContentMetadataSchema = z.object({
metadata: z
.record(z.any())
.optional()
.describe('Additional metadata'),
});
export const MessageContentSchema = z.discriminatedUnion('type', [
// Plain text content
BaseMessageContentMetadataSchema.extend({
type: z.literal('text'),
text: z.string().describe('Text content'),
}),
// Image content (for vision models), with optional caption/alt text
BaseMessageContentMetadataSchema.extend({
type: z.literal('image'),
imageUrl: z.string().url().describe('Image URL for vision models'),
text: z.string().optional().describe('Alt text or caption for the image'),
}),
// File attachment content
BaseMessageContentMetadataSchema.extend({
type: z.literal('file'),
fileUrl: z.string().url().describe('File attachment URL'),
mimeType: z.string().optional().describe('MIME type for files'),
text: z.string().optional().describe('Optional description of the file'),
}),
// Code content (source code snippet)
BaseMessageContentMetadataSchema.extend({
type: z.literal('code'),
text: z.string().describe('Code content'),
mimeType: z
.string()
.optional()
.describe('MIME type or language identifier for the code'),
}),
// Structured content (JSON-like payloads, tables, etc.)
BaseMessageContentMetadataSchema.extend({
type: z.literal('structured'),
text: z.string().optional().describe('Optional human-readable summary of the structured content'),
}),
]);

Copilot uses AI. Check for mistakes.
strategy: TokenBudgetStrategySchema.default('sliding_window'),

/** Strategy-Specific Options */
slidingWindowSize: z.number().int().positive().optional().describe('Number of recent messages to keep'),
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

TokenBudgetConfigSchema defaults strategy to sliding_window, but slidingWindowSize is optional, which makes the default configuration ambiguous (it’s not clear what window size applies). Consider either (a) providing a sensible default for slidingWindowSize, or (b) making the config a discriminated union so slidingWindowSize is required when strategy === "sliding_window" (and similarly require minImportanceScore/semanticThreshold for their strategies).

Suggested change
slidingWindowSize: z.number().int().positive().optional().describe('Number of recent messages to keep'),
slidingWindowSize: z.number().int().positive().default(50).describe('Number of recent messages to keep'),

Copilot uses AI. Check for mistakes.
*/
export const ConversationContextSchema = z.object({
/** Identity */
sessionId: z.string().describe('Conversation session ID'),
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

ConversationSessionSchema contains both context.sessionId and a top-level id, but there’s no guarantee they match. This can lead to inconsistent session identifiers across the protocol. Consider removing sessionId from ConversationContextSchema (derive it from ConversationSession.id), or add validation to enforce equality.

Suggested change
sessionId: z.string().describe('Conversation session ID'),

Copilot uses AI. Check for mistakes.
Comment on lines +96 to +99
/** Period */
period: BillingPeriodSchema,
customPeriodDays: z.number().int().positive().optional().describe('Custom period in days'),

Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

BudgetLimitSchema allows period: "custom" without customPeriodDays, even though the schema describes customPeriodDays as the custom period definition. Consider enforcing that customPeriodDays is required when period === "custom" (and ideally disallow it for non-custom periods) so invalid budget configurations don’t validate.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants