@adaptiqai/deepseek-harness 0.2.18 → 0.2.20

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.
@@ -5,8 +5,8 @@
5
5
  * the server keeps one native memory per conversation.
6
6
  */
7
7
  import { LlmAdapter, LlmError } from '@deepseek-ai/dsh-llm';
8
- import type { GenerateOptions, LlmModelInfo, LlmProviderInfo, LlmResolvedModelInfo, PreparedAdapterCall, ResolvedRetryPolicy, StreamChunk } from '@deepseek-ai/dsh-llm';
9
- import { type AdaptiqFailure, type AuthManager, type CatalogModel, type RequestEvents, type UserTurn } from '@adaptiqai/core';
8
+ import type { GenerateOptions, LlmModelInfo, LlmProviderInfo, LlmResolvedModelInfo, PreparedAdapterCall, RequestMessage, ResolvedRetryPolicy, StreamChunk } from '@deepseek-ai/dsh-llm';
9
+ import { type AdaptiqFailure, type AuthManager, type CatalogModel, PREPARING_GAVE_UP, type PreparingWhat, type RequestEvents, type UserTurn } from '@adaptiqai/core';
10
10
  import { type ImageLimits, type ImageRef, type ImageSource } from './images.ts';
11
11
  import { type ParentSession, type ReferencedSession } from './wire.ts';
12
12
  /** What the adapter tells the rest of the plugin about each request. */
@@ -38,11 +38,22 @@ export interface AdapterDeps {
38
38
  attachPending?(sessionId: string): Promise<void>;
39
39
  /** What the person wrote in this conversation (their own messages, marked when they answer an offer to save knowledge), for the tools' guards. */
40
40
  noteUserTexts?(sessionId: string, texts: readonly UserTurn[]): void;
41
+ /** The conversation's whole history, as the harness sent it, for find in it. */
42
+ noteConversation?(sessionId: string, messages: readonly RequestMessage[]): void;
41
43
  /** The session that started this one (a subagent's or teammate's parent), if any. */
42
44
  parentSession?(sessionId: string): ParentSession | undefined;
43
45
  readonly events: RequestEvents;
44
46
  /** Whether the server behind a model puts idle conversations to sleep (it reports session history apart). */
45
47
  readonly autoSleep?: (model: string) => boolean;
48
+ /** A model's conversation memory when it is full: what is taken and its size. */
49
+ readonly sessionMemoryFull?: (model: string) => Promise<{
50
+ readonly usedTokens: number;
51
+ readonly limitTokens: number;
52
+ } | null>;
53
+ /** The collections a conversation uses, for the words while it gets ready. */
54
+ readonly preparing?: (sessionId: string) => PreparingWhat | null;
55
+ /** Waits `ms` unless the signal stops it first (tests pass their own). */
56
+ readonly sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
46
57
  /** The harness's attachment service, where attached images are stored (absent on hosts without one). */
47
58
  attachments?(): ImageSource | undefined;
48
59
  /** Where the model's tools can read the stored copy of an image. */
@@ -63,8 +74,9 @@ export declare const ATTACH_WAIT_MS = 10000;
63
74
  * answer is the same each time and one toast says it.
64
75
  */
65
76
  export declare const COMPACTION_NOTICE_MS: number;
66
- /** The harness retries busy and preparing answers itself, honouring Retry-After. */
67
77
  export declare const RETRY_POLICY: ResolvedRetryPolicy;
78
+ /** The last "getting ready" answer of a turn, in the core's words for every harness. */
79
+ export { PREPARING_GAVE_UP };
68
80
  /**
69
81
  * Exact-route model info the harness reads (context window, efforts, output default).
70
82
  * @param route - provider route id.
@@ -80,7 +92,22 @@ export declare class AdaptiqAdapter extends LlmAdapter {
80
92
  private readonly fetch;
81
93
  /** When each conversation was last told that nothing needs compacting. */
82
94
  private readonly compactionTold;
95
+ /** Each conversation's "getting ready" answers in a row: how many, for which of the person's messages, and when the last came. */
96
+ private readonly preparingRun;
97
+ /** What each conversation's next "getting ready" wait holds past the harness's longest, waited here. */
98
+ private readonly preparingHold;
83
99
  constructor(deps: AdapterDeps, now?: () => number);
100
+ /**
101
+ * A conversation's failure, counted in its run of "getting ready" answers
102
+ * for the same message of the person: the one after the harness's last
103
+ * retry says that nothing was answered, instead of promising another try.
104
+ * Any other answer ends the run.
105
+ * @param sessionId - the conversation.
106
+ * @param turn - how many messages of the person the request holds, when known.
107
+ * @param failure - the failure as classified.
108
+ * @returns the failure, worded for the last try when it is one.
109
+ */
110
+ private lastTry;
84
111
  providerInfo(provider: string): LlmProviderInfo;
85
112
  providerRetryPolicy(): ResolvedRetryPolicy;
86
113
  listModels(provider: string): Promise<readonly LlmModelInfo[]>;
@@ -0,0 +1,106 @@
1
+ import { type knowledgeReading, type ReadingArgs, type Workspace } from '@adaptiqai/core';
2
+ import type { ConversationTexts } from './conversations.ts';
3
+ /** One logged event of a session, as far as a find reads it. */
4
+ interface LoggedEvent {
5
+ readonly type: string;
6
+ readonly seq: number;
7
+ /** Unix epoch milliseconds. */
8
+ readonly time: number;
9
+ readonly data?: unknown;
10
+ }
11
+ /** One exact cut of a session: a live one copies its log only when `events` is read. */
12
+ interface Observation {
13
+ readonly events: readonly LoggedEvent[];
14
+ /** The last event's seq, or -1 for an empty log. */
15
+ readonly cursor: number;
16
+ /** The stored copy's revision, for a session that is not open. */
17
+ readonly revision?: string;
18
+ [Symbol.dispose]?(): void;
19
+ }
20
+ /**
21
+ * The harness's session reads (`sessionQuery`). Newer hosts observe a session's
22
+ * whole log; older ones read its current surface, where a compacted part is
23
+ * only its summary (AdaptiqAI's own conversations are never compacted).
24
+ */
25
+ export interface SessionReads {
26
+ observeSession?(sessionId: string, options: {
27
+ readonly projectionMode: 'none';
28
+ readonly signal?: AbortSignal;
29
+ }): Promise<Observation>;
30
+ readSurface?(sessionId: string): Promise<{
31
+ readonly capturedThroughSeq: number | null;
32
+ readonly events: readonly LoggedEvent[];
33
+ }>;
34
+ }
35
+ /** How far a conversation is read: the last event a mention captured, and when it was made private. */
36
+ interface Limits {
37
+ readonly through: number | undefined;
38
+ readonly since: number | null;
39
+ }
40
+ /** A conversation's lines, and what of it was left out. */
41
+ interface Read {
42
+ readonly lines: readonly string[];
43
+ /** How many of the person's messages the lines hold: the version a next page keeps to. */
44
+ readonly messages: number;
45
+ /** Messages written there after the mention captured it. */
46
+ readonly newer: boolean;
47
+ /** Messages written there after it was made private. */
48
+ readonly unsaved: boolean;
49
+ }
50
+ type Outcome = {
51
+ readonly kind: 'read';
52
+ readonly read: Read;
53
+ } | {
54
+ readonly kind: 'missing' | 'failed' | 'unsupported';
55
+ };
56
+ /**
57
+ * What the person wrote in a session, as lines: each message headed with its
58
+ * number, its text a line each, an empty line after it. The harness's
59
+ * injected context, @ snapshots, compaction summaries, the plugin's notes,
60
+ * the agent's replies, tool calls and results are left out.
61
+ * @param events - the session's logged events, oldest first.
62
+ * @param limits - the last event a mention captured, and when it was made private.
63
+ * @returns the lines, and what was left out past the limits.
64
+ */
65
+ export declare function addedLines(events: readonly LoggedEvent[], limits: Limits): Read;
66
+ /** The added conversations' lines, read through the harness and kept while they do not change. */
67
+ export declare class AddedConversations {
68
+ private readonly reads;
69
+ private readonly kept;
70
+ private chars;
71
+ /** @param reads - the harness's session reads, when it has them. */
72
+ constructor(reads: () => SessionReads | undefined);
73
+ /**
74
+ * A conversation's lines, read again only when it changed (its cursor or
75
+ * revision) or its limits did.
76
+ * @param sessionId - the conversation.
77
+ * @param limits - how far it is read.
78
+ * @param signal - cancellation.
79
+ * @returns its lines; missing when the harness has no such conversation here, failed when it could not be read, unsupported when this harness reads no conversations.
80
+ */
81
+ read(sessionId: string, limits: Limits, signal?: AbortSignal): Promise<Outcome>;
82
+ /** A kept read of a conversation as it is now, which becomes the most recently used. */
83
+ private recall;
84
+ /** Keep a read; the least recently used go past four conversations or 24M characters, never the one just read. */
85
+ private keep;
86
+ }
87
+ /** What find_in_conversation reads with: the workspace, this conversation's history, the added ones' reads and core's listing. */
88
+ export interface AddedFind {
89
+ readonly workspace: Workspace;
90
+ readonly conversations: ConversationTexts | undefined;
91
+ readonly added: AddedConversations;
92
+ readonly reader: Pick<ReturnType<typeof knowledgeReading>, 'findInConversations'>;
93
+ }
94
+ /**
95
+ * Find in a conversation added here, by its name, or in this one and every
96
+ * added one together ("all"), as one list. What could not be searched is
97
+ * said under the page's first line.
98
+ * @param context - what it reads with.
99
+ * @param sessionId - the conversation the find runs in.
100
+ * @param which - a conversation's name, or "all".
101
+ * @param args - the words, and more, fields, sort, sum and group as for find.
102
+ * @param signal - cancellation.
103
+ * @returns one page.
104
+ */
105
+ export declare function findInAdded(context: AddedFind, sessionId: string, which: string, args: ReadingArgs, signal?: AbortSignal): Promise<string>;
106
+ export {};
@@ -1,16 +1,12 @@
1
1
  /** `/adaptiq`: status, sign-in, privacy and knowledge from the composer, in plain text. */
2
2
  import type { Context } from '@deepseek-ai/cordis';
3
- import { type AuthManager, type Workspace } from '@adaptiqai/core';
3
+ import { type AuthManager, commandAction, type Workspace } from '@adaptiqai/core';
4
+ /** DeepSeek Harness's own words: its Settings page, its picker and its Knowledge panel. */
5
+ export declare const WORDS: import("@adaptiqai/core").CommandWords;
4
6
  export declare const HELP: string;
5
7
  /** The actions the composer offers after `/adaptiq`: every one HELP lists. */
6
- export declare const HINT = "status \u00B7 signin \u00B7 private \u00B7 remember \u00B7 knowledge \u00B7 open \u00B7 diagnostics \u00B7 signout";
7
- /**
8
- * The action word of `/adaptiq …`, however the composer passes it: with or
9
- * without the command name, a leading slash, or a completion glued on with "/".
10
- * @param rawInput - text after (or including) the command name.
11
- * @returns the action, lower case; "" for none.
12
- */
13
- export declare function commandAction(rawInput: string): string;
8
+ export declare const HINT: string;
9
+ export { commandAction };
14
10
  /**
15
11
  * Register `/adaptiq`.
16
12
  * @param ctx - context with the `commands` service.
@@ -0,0 +1,28 @@
1
+ import type { RequestMessage } from '@deepseek-ai/dsh-llm';
2
+ export declare class ConversationTexts {
3
+ private readonly held;
4
+ /**
5
+ * Keep a conversation's history, as the harness sent it for a request.
6
+ * @param sessionId - the conversation.
7
+ * @param messages - its history.
8
+ */
9
+ note(sessionId: string, messages: readonly RequestMessage[]): void;
10
+ /**
11
+ * A conversation's history, as the harness last sent it.
12
+ * @param sessionId - the conversation.
13
+ * @returns its messages, or null when no request of it was seen.
14
+ */
15
+ messages(sessionId: string): readonly RequestMessage[] | null;
16
+ /**
17
+ * A conversation's messages as lines, and a version that changes when the
18
+ * person writes again: each step of a turn adds the agent's call and its
19
+ * result, and a version of all the messages sent every next page back to
20
+ * the first.
21
+ * @param sessionId - the conversation.
22
+ * @returns them, or null when no request of it was seen.
23
+ */
24
+ lines(sessionId: string): {
25
+ readonly lines: readonly string[];
26
+ readonly version: string;
27
+ } | null;
28
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * A turn is told which collections its conversation reads, once and again
3
+ * when they change. Nothing else in a turn says a collection is in use, so an
4
+ * agent asked about an id in one searched the person's files instead.
5
+ *
6
+ * The note is a sourced user message the agent loop logs with the turn's
7
+ * first step, as the harness's own context producers add theirs: the model
8
+ * reads it in every later request, and the conversation view shows it as
9
+ * context, not as the person's words.
10
+ */
11
+ import type { Context } from '@deepseek-ai/cordis';
12
+ import { type ContextFormed, type UserMessage } from '@deepseek-ai/dsh-llm';
13
+ import { type TurnNotes, type Workspace } from '@adaptiqai/core';
14
+ /** Who produced the note, in the message's source. */
15
+ export declare const NOTE_KIND = "adaptiq-knowledge";
16
+ declare module '@deepseek-ai/dsh-llm' {
17
+ interface MessageSourceMap {
18
+ 'adaptiq-knowledge': {
19
+ kind: 'adaptiq-knowledge';
20
+ } & ContextFormed;
21
+ }
22
+ }
23
+ /** One logged event of a session, as far as the note reads it. */
24
+ interface LoggedEvent {
25
+ readonly type?: string;
26
+ readonly data?: {
27
+ readonly source?: {
28
+ readonly kind?: unknown;
29
+ };
30
+ readonly content?: readonly {
31
+ readonly type?: string;
32
+ readonly text?: unknown;
33
+ }[];
34
+ };
35
+ }
36
+ /** The agent proposing a step: its route and model, and its session's log. */
37
+ interface NoteAgent {
38
+ readonly options: {
39
+ readonly provider?: string;
40
+ readonly model?: string;
41
+ };
42
+ readonly session: {
43
+ readonly id: string;
44
+ readonly seq?: number;
45
+ eventAt?(seq: number): LoggedEvent | undefined;
46
+ };
47
+ }
48
+ interface PreStep {
49
+ readonly agent: NoteAgent;
50
+ readonly step: number;
51
+ readonly signal: AbortSignal;
52
+ }
53
+ /**
54
+ * The last note a session's log holds, so a conversation resumed after a
55
+ * restart is not told twice.
56
+ * @param agent - the agent whose session it is.
57
+ * @returns the note's text, or null.
58
+ */
59
+ export declare function loggedNote(agent: NoteAgent): string | null;
60
+ /**
61
+ * The note for a step: on the first step of a turn on AdaptiqAI, when there is news.
62
+ * @param notes - the turn notes.
63
+ * @param route - the AdaptiqAI route.
64
+ * @param payload - the proposed step.
65
+ * @returns the note's message, or null.
66
+ */
67
+ export declare function noteFor(notes: TurnNotes, route: string, payload: PreStep): Promise<UserMessage | null>;
68
+ /**
69
+ * Tell each turn on AdaptiqAI which collections its conversation reads.
70
+ * @param ctx - a context with the `agents` service.
71
+ * @param workspace - the workspace.
72
+ * @param route - the AdaptiqAI route.
73
+ */
74
+ export declare function registerKnowledgeNote(ctx: Context, workspace: Workspace, route: string): void;
75
+ export {};
@@ -1,14 +1,28 @@
1
1
  import type { Context } from '@deepseek-ai/cordis';
2
+ import type { ConversationTexts } from './conversations.ts';
2
3
  import { type Workspace } from '@adaptiqai/core';
4
+ import { type ToolClock } from '@adaptiqai/core';
3
5
  /**
4
- * The media type for a file name.
6
+ * The media type for a file. A text of any other name (a Containerfile, a
7
+ * Makefile, JSON Lines, a .env) goes as plain text: sent as bytes, the
8
+ * engine refused it ("is not a supported source type yet") and the whole
9
+ * upload with it. The engine still reads a file's kind from its ending, a
10
+ * .tsv as a table.
5
11
  * @param name - file name.
6
- * @returns a media type, `text/plain` for source code.
12
+ * @param content - its bytes, to tell text from binary where the name does not.
13
+ * @returns a media type, `text/plain` for source code and other text.
7
14
  */
8
- export declare function mediaTypeOf(name: string): string;
15
+ export declare function mediaTypeOf(name: string, content?: Uint8Array): string;
9
16
  /**
10
17
  * Register the AdaptiqAI agent tools.
11
18
  * @param ctx - context with the `tools` service.
12
19
  * @param workspace - the workspace.
13
20
  */
14
- export declare function registerTools(ctx: Context, workspace: Workspace): void;
21
+ /**
22
+ * Register the agent tools.
23
+ * @param ctx - the plugin's context.
24
+ * @param workspace - the AdaptiqAI workspace.
25
+ * @param conversations - each conversation's history as the adapter last sent it, for find_in_conversation.
26
+ * @param clock - how a call waits before asking again (tests pass their own).
27
+ */
28
+ export declare function registerTools(ctx: Context, workspace: Workspace, conversations?: ConversationTexts, clock?: ToolClock): void;
@@ -62,6 +62,8 @@ export interface WireExtras {
62
62
  export interface ReferencedSession {
63
63
  readonly sessionId: string;
64
64
  readonly label: string;
65
+ /** The last event of it the snapshot captured (-1 when it was empty), when the harness recorded that. */
66
+ readonly capturedThroughSeq?: number;
65
67
  }
66
68
  /**
67
69
  * The sessions a harness `## Referenced sessions` snapshot carries, read from
@@ -71,7 +73,8 @@ export interface ReferencedSession {
71
73
  */
72
74
  export declare function sessionReferences(message: RequestMessage): ReferencedSession[] | null;
73
75
  /**
74
- * Every session the request's history mentions, first mention first.
76
+ * Every session the request's history mentions, first mention first, each
77
+ * with the most of it a mention captured: a later mention shows more of it.
75
78
  * @param messages - the request history.
76
79
  * @returns unique referenced sessions.
77
80
  */
@@ -103,10 +106,39 @@ export declare function userTexts(messages: readonly RequestMessage[]): string[]
103
106
  /**
104
107
  * What the person wrote in the request history, each message marked when it
105
108
  * answers the agent's offer to save knowledge (the agent asked right before).
109
+ * The offer is recognised in core, the same in every harness.
106
110
  * @param messages - the request history.
107
111
  * @returns the person's turns, oldest first.
108
112
  */
109
113
  export declare function userTurns(messages: readonly RequestMessage[]): UserTurn[];
114
+ /**
115
+ * What a turn with the knowledge tool is told about its own conversation.
116
+ * Asked for the fines over 40 operations notes, MiMo tried to recall every
117
+ * note in its reasoning, ran ten minutes and answered nothing; the tool's
118
+ * description spoke of saved knowledge, and it never used
119
+ * find_in_conversation.
120
+ */
121
+ export declare const CONVERSATION_INSTRUCTIONS = "This conversation and those added to it: to count, add up or list what was said or noted earlier in them (notes, logs, figures, names), use adaptiq_knowledge, action find_in_conversation, with the words to look for, and answer from the lines it returns; for a conversation added here, give its name as conversation, or \"all\" for this one and every added one together. A long conversation is read in parts, so never count from what you remember of it.";
122
+ /** The instructions a turn with the knowledge tool gets, collections first. */
123
+ export declare const TURN_INSTRUCTIONS = "AdaptiqAI memory: before you answer about a specific id, name, number or date, look it up with adaptiq_knowledge, action find, with the id or the words, and answer from the lines it returns, citing document:line. Do that first: a collection this conversation uses (adaptiq_knowledge status names it) is in your memory, not on disk, so grep, glob, read and bash do not reach its documents. Each reply shows you only the part of it that matches the conversation, never all of it: documents you do not see are still there. adaptiq_knowledge is not the terminal, a local file, the web, an earlier chat or a subagent, so use it also when the person asks you to answer from memory without those. Never name a document as a source unless find or open showed it to you; if find fails, say what it answered. This conversation and those added to it: to count, add up or list what was said or noted earlier in them (notes, logs, figures, names), use adaptiq_knowledge, action find_in_conversation, with the words to look for, and answer from the lines it returns; for a conversation added here, give its name as conversation, or \"all\" for this one and every added one together. A long conversation is read in parts, so never count from what you remember of it.";
124
+ /**
125
+ * Whether a message is one the person wrote: typed, or a reply to the
126
+ * agent's question. The harness also sends the user role its own context
127
+ * (instructions, skills, checkpoints, the plugin's notes, conversations
128
+ * mentioned with @), and the agent's replies repeat figures: counted
129
+ * together, one typed fine became three.
130
+ * @param message - a message of the request history.
131
+ * @returns whether the person wrote it.
132
+ */
133
+ export declare function personWrote(message: RequestMessage): boolean;
134
+ /**
135
+ * A conversation's own messages as lines, for find in it: what the person
136
+ * wrote, each message headed with its number, its text a line each, an empty
137
+ * line after it.
138
+ * @param messages - the request history, as the harness sent it.
139
+ * @returns the lines.
140
+ */
141
+ export declare function conversationLines(messages: readonly RequestMessage[]): string[];
110
142
  /**
111
143
  * Convert the harness history to Chat Completions messages.
112
144
  * @param options - the harness request.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adaptiqai/deepseek-harness",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "description": "AdaptiqAI for DeepSeek Harness: models with 5M-token native memory and Knowledge. Sign up, get a model and work without leaving the harness",
5
5
  "keywords": [
6
6
  "dsh-plugin",
@@ -90,7 +90,7 @@
90
90
  "react-dom": "~18.3.1",
91
91
  "typescript": "~5.9.3",
92
92
  "vitest": "~3.2.7",
93
- "@adaptiqai/core": "0.2.14"
93
+ "@adaptiqai/core": "0.2.16"
94
94
  },
95
95
  "repository": {
96
96
  "type": "git",