@graphorin/core 0.6.1 → 0.7.0
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.
- package/CHANGELOG.md +82 -0
- package/README.md +8 -5
- package/dist/channels/index.d.ts +2 -2
- package/dist/channels/index.js +2 -2
- package/dist/channels/pause.d.ts +47 -2
- package/dist/channels/pause.d.ts.map +1 -1
- package/dist/channels/pause.js +62 -2
- package/dist/channels/pause.js.map +1 -1
- package/dist/contracts/checkpoint-store.d.ts +97 -1
- package/dist/contracts/checkpoint-store.d.ts.map +1 -1
- package/dist/contracts/checkpoint-store.js.map +1 -1
- package/dist/contracts/index.d.ts +5 -5
- package/dist/contracts/index.js +2 -1
- package/dist/contracts/memory-store.d.ts +59 -2
- package/dist/contracts/memory-store.d.ts.map +1 -1
- package/dist/contracts/provider.d.ts +20 -6
- package/dist/contracts/provider.d.ts.map +1 -1
- package/dist/contracts/session-store.d.ts +10 -2
- package/dist/contracts/session-store.d.ts.map +1 -1
- package/dist/contracts/tool.d.ts +75 -1
- package/dist/contracts/tool.d.ts.map +1 -1
- package/dist/contracts/tool.js +57 -0
- package/dist/contracts/tool.js.map +1 -0
- package/dist/contracts/tracer.d.ts +53 -5
- package/dist/contracts/tracer.d.ts.map +1 -1
- package/dist/contracts/tracer.js.map +1 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/dist/types/agent-event-wire.d.ts +74 -0
- package/dist/types/agent-event-wire.d.ts.map +1 -0
- package/dist/types/agent-event-wire.js +130 -0
- package/dist/types/agent-event-wire.js.map +1 -0
- package/dist/types/agent-event.d.ts +48 -6
- package/dist/types/agent-event.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -2
- package/dist/types/index.js +2 -1
- package/dist/types/memory.d.ts +11 -0
- package/dist/types/memory.d.ts.map +1 -1
- package/dist/types/run.d.ts +86 -4
- package/dist/types/run.d.ts.map +1 -1
- package/dist/types/run.js.map +1 -1
- package/dist/types/tool.d.ts +10 -0
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/types/usage.d.ts +11 -1
- package/dist/types/usage.d.ts.map +1 -1
- package/dist/types/usage.js.map +1 -1
- package/dist/utils/binary-json.d.ts +165 -0
- package/dist/utils/binary-json.d.ts.map +1 -0
- package/dist/utils/binary-json.js +240 -0
- package/dist/utils/binary-json.js.map +1 -0
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/validation.d.ts +10 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +1 -1
- package/dist/utils/validation.js.map +1 -1
- package/package.json +9 -7
- package/src/channels/channels.ts +206 -0
- package/src/channels/directive.ts +41 -0
- package/src/channels/dispatch.ts +37 -0
- package/src/channels/durable.ts +151 -0
- package/src/channels/index.ts +62 -0
- package/src/channels/pause.ts +216 -0
- package/src/contracts/auth-token-store.ts +42 -0
- package/src/contracts/checkpoint-store.ts +256 -0
- package/src/contracts/embedder.ts +42 -0
- package/src/contracts/eval-scorer.ts +44 -0
- package/src/contracts/index.ts +112 -0
- package/src/contracts/local-provider-trust.ts +33 -0
- package/src/contracts/logger.ts +61 -0
- package/src/contracts/memory-store.ts +187 -0
- package/src/contracts/oauth-server-store.ts +78 -0
- package/src/contracts/preferred-model.ts +56 -0
- package/src/contracts/provider.ts +316 -0
- package/src/contracts/reasoning-retention.ts +52 -0
- package/src/contracts/redaction-validator.ts +56 -0
- package/src/contracts/sandbox.ts +70 -0
- package/src/contracts/secret-ref.ts +22 -0
- package/src/contracts/secret-value.ts +117 -0
- package/src/contracts/secrets-store.ts +90 -0
- package/src/contracts/session-store.ts +163 -0
- package/src/contracts/token-counter.ts +23 -0
- package/src/contracts/tool.ts +397 -0
- package/src/contracts/tracer.ts +219 -0
- package/src/contracts/trigger-store.ts +40 -0
- package/src/index.ts +23 -0
- package/src/types/agent-event-wire.ts +193 -0
- package/src/types/agent-event.ts +579 -0
- package/src/types/handoff.ts +111 -0
- package/src/types/index.ts +148 -0
- package/src/types/memory.ts +427 -0
- package/src/types/message.ts +174 -0
- package/src/types/run.ts +312 -0
- package/src/types/sensitivity.ts +35 -0
- package/src/types/session-scope.ts +18 -0
- package/src/types/stop-condition.ts +108 -0
- package/src/types/tool-call.ts +24 -0
- package/src/types/tool.ts +324 -0
- package/src/types/usage.ts +120 -0
- package/src/types/workflow-event.ts +132 -0
- package/src/utils/assert-never.ts +24 -0
- package/src/utils/async-context.ts +55 -0
- package/src/utils/binary-json.ts +425 -0
- package/src/utils/hash.ts +122 -0
- package/src/utils/index.ts +57 -0
- package/src/utils/streams.ts +233 -0
- package/src/utils/validation.ts +82 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { SessionScope } from '../types/session-scope.js';
|
|
2
|
+
import type { SecretRef } from './secret-ref.js';
|
|
3
|
+
import type { SecretValue } from './secret-value.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Pluggable secret resolver - turns a parsed `SecretRef` into a live
|
|
7
|
+
* `SecretValue`. Concrete resolvers live in `@graphorin/security` (env,
|
|
8
|
+
* keyring, file, encrypted-file, literal, ref, vault) and in optional
|
|
9
|
+
* adapter packages (`@graphorin/secret-1password`, …).
|
|
10
|
+
*
|
|
11
|
+
* @stable
|
|
12
|
+
*/
|
|
13
|
+
export interface SecretResolver {
|
|
14
|
+
/** Lowercased URI scheme handled by this resolver (`'env'`, `'op'`, …). */
|
|
15
|
+
readonly scheme: string;
|
|
16
|
+
resolve(ref: SecretRef, ctx?: SecretResolverContext): Promise<SecretValue>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Optional context handed to a resolver. Carries the originating tool /
|
|
21
|
+
* agent identifiers so the audit log can attribute the resolution.
|
|
22
|
+
*
|
|
23
|
+
* @stable
|
|
24
|
+
*/
|
|
25
|
+
export interface SecretResolverContext {
|
|
26
|
+
readonly toolName?: string;
|
|
27
|
+
readonly agentId?: string;
|
|
28
|
+
readonly runId?: string;
|
|
29
|
+
readonly signal?: AbortSignal;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Pluggable secret-managing storage. Concrete implementations live in
|
|
34
|
+
* `@graphorin/security` (`KeyringSecretsStore`, `EncryptedFileSecretsStore`,
|
|
35
|
+
* `EnvSecretsStore`, `MemorySecretsStore`).
|
|
36
|
+
*
|
|
37
|
+
* The interface is intentionally narrow: every method either returns a
|
|
38
|
+
* `SecretValue` or a piece of metadata that is safe to log. The raw
|
|
39
|
+
* value is never returned as a `string` from this surface.
|
|
40
|
+
*
|
|
41
|
+
* @stable
|
|
42
|
+
*/
|
|
43
|
+
export interface SecretsStore {
|
|
44
|
+
/** Returns the secret if it exists, `null` otherwise. */
|
|
45
|
+
get(key: string, scope?: SessionScope): Promise<SecretValue | null>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Returns the secret or throws. Implementations enforce the per-tool
|
|
49
|
+
* `secretsAllowed` ACL: if the current tool context disallows `key`,
|
|
50
|
+
* throw `SecretAccessDeniedError`.
|
|
51
|
+
*/
|
|
52
|
+
require(key: string, scope?: SessionScope): Promise<SecretValue>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Persist a secret. Implementations auto-wrap a plain string into a
|
|
56
|
+
* `SecretValue` so callers don't have to.
|
|
57
|
+
*/
|
|
58
|
+
set(key: string, value: string | SecretValue, opts?: SecretsSetOptions): Promise<void>;
|
|
59
|
+
|
|
60
|
+
delete(key: string, scope?: SessionScope): Promise<void>;
|
|
61
|
+
|
|
62
|
+
/** Returns metadata about every key - never the values themselves. */
|
|
63
|
+
list(scope?: SessionScope): Promise<ReadonlyArray<SecretMetadata>>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Optional knobs for `SecretsStore.set(...)`.
|
|
68
|
+
*
|
|
69
|
+
* @stable
|
|
70
|
+
*/
|
|
71
|
+
export interface SecretsSetOptions {
|
|
72
|
+
readonly scope?: SessionScope;
|
|
73
|
+
readonly expiresAt?: string;
|
|
74
|
+
readonly tags?: ReadonlyArray<string>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Public metadata about a stored secret. Safe to log - never carries the
|
|
79
|
+
* value itself.
|
|
80
|
+
*
|
|
81
|
+
* @stable
|
|
82
|
+
*/
|
|
83
|
+
export interface SecretMetadata {
|
|
84
|
+
readonly key: string;
|
|
85
|
+
readonly createdAt: string;
|
|
86
|
+
readonly updatedAt?: string;
|
|
87
|
+
readonly expiresAt?: string;
|
|
88
|
+
readonly tags?: ReadonlyArray<string>;
|
|
89
|
+
readonly source?: string;
|
|
90
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import type { HandoffRecord } from '../types/handoff.js';
|
|
2
|
+
import type { SessionScope } from '../types/session-scope.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Lightweight session metadata persisted by the sessions package. The
|
|
6
|
+
* actual `session_messages` rows are owned by `MemoryStore` (single source
|
|
7
|
+
* of truth - the sessions package delegates message CRUD to memory).
|
|
8
|
+
*
|
|
9
|
+
* @stable
|
|
10
|
+
*/
|
|
11
|
+
export interface SessionMetadata {
|
|
12
|
+
readonly id: string;
|
|
13
|
+
readonly userId: string;
|
|
14
|
+
readonly agentId: string;
|
|
15
|
+
readonly title?: string;
|
|
16
|
+
readonly createdAt: string;
|
|
17
|
+
readonly updatedAt?: string;
|
|
18
|
+
readonly closedAt?: string;
|
|
19
|
+
readonly tags?: ReadonlyArray<string>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Agent registry entry. Captures stable metadata about every agent that
|
|
24
|
+
* ever produced a message - so JSONL exports / replays can resolve a
|
|
25
|
+
* `Message.agentId` to a human-readable name even after the agent was
|
|
26
|
+
* renamed or retired.
|
|
27
|
+
*
|
|
28
|
+
* @stable
|
|
29
|
+
*/
|
|
30
|
+
export interface AgentRegistryEntry {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly displayName: string;
|
|
33
|
+
readonly registeredAt: string;
|
|
34
|
+
readonly retiredAt?: string;
|
|
35
|
+
readonly tags?: ReadonlyArray<string>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Workflow ↔ session mapping row. Lets the server enumerate the
|
|
40
|
+
* workflows attached to a session for resume / replay flows.
|
|
41
|
+
*
|
|
42
|
+
* @stable
|
|
43
|
+
*/
|
|
44
|
+
export interface SessionWorkflowRun {
|
|
45
|
+
readonly sessionId: string;
|
|
46
|
+
readonly workflowId: string;
|
|
47
|
+
readonly threadId: string;
|
|
48
|
+
readonly attachedAt: string;
|
|
49
|
+
readonly status: 'running' | 'suspended' | 'completed' | 'failed';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Session lifecycle audit event. The `@graphorin/sessions` package
|
|
54
|
+
* appends one row per noteworthy lifecycle step (`created`, `closed`,
|
|
55
|
+
* `forked`, `replayed`, `cassette-recorded`, `cassette-replayed`,
|
|
56
|
+
* `commentary-sanitized`, …) plus per-session-handoff. Adapters can
|
|
57
|
+
* surface the rows verbatim from disk.
|
|
58
|
+
*
|
|
59
|
+
* The `metadata` field is intentionally an open record - storage
|
|
60
|
+
* adapters serialize it as JSON. Callers should keep it small and
|
|
61
|
+
* never include secret values.
|
|
62
|
+
*
|
|
63
|
+
* @stable
|
|
64
|
+
*/
|
|
65
|
+
export interface SessionAuditEntry {
|
|
66
|
+
readonly id: string;
|
|
67
|
+
readonly sessionId: string;
|
|
68
|
+
readonly action: string;
|
|
69
|
+
readonly at: string;
|
|
70
|
+
readonly actor?: {
|
|
71
|
+
readonly kind: string;
|
|
72
|
+
readonly id: string;
|
|
73
|
+
readonly label?: string;
|
|
74
|
+
};
|
|
75
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Pluggable session-metadata storage. Implementations live in the
|
|
80
|
+
* storage adapter packages.
|
|
81
|
+
*
|
|
82
|
+
* @stable
|
|
83
|
+
*/
|
|
84
|
+
export interface SessionStore {
|
|
85
|
+
createSession(metadata: SessionMetadata): Promise<void>;
|
|
86
|
+
getSession(sessionId: string): Promise<SessionMetadata | null>;
|
|
87
|
+
listSessions(
|
|
88
|
+
scope: Pick<SessionScope, 'userId' | 'agentId'>,
|
|
89
|
+
): Promise<ReadonlyArray<SessionMetadata>>;
|
|
90
|
+
updateSession(sessionId: string, patch: Partial<SessionMetadata>): Promise<void>;
|
|
91
|
+
closeSession(sessionId: string, closedAt: string): Promise<void>;
|
|
92
|
+
|
|
93
|
+
registerAgent(entry: AgentRegistryEntry): Promise<void>;
|
|
94
|
+
retireAgent(agentId: string, retiredAt: string): Promise<void>;
|
|
95
|
+
resolveAgent(agentId: string): Promise<AgentRegistryEntry | null>;
|
|
96
|
+
|
|
97
|
+
appendHandoff(sessionId: string, record: HandoffRecord): Promise<void>;
|
|
98
|
+
listHandoffs(sessionId: string): Promise<ReadonlyArray<HandoffRecord>>;
|
|
99
|
+
|
|
100
|
+
attachWorkflowRun(run: SessionWorkflowRun): Promise<void>;
|
|
101
|
+
listWorkflowRuns(sessionId: string): Promise<ReadonlyArray<SessionWorkflowRun>>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Optional extension surface for storage adapters that expose the
|
|
106
|
+
* additional capabilities `@graphorin/sessions` consumes.
|
|
107
|
+
* Adapters that opt out leave the property undefined; the sessions
|
|
108
|
+
* facade degrades gracefully (delete becomes retire; audit rows are
|
|
109
|
+
* dropped on the floor with a one-time WARN).
|
|
110
|
+
*
|
|
111
|
+
* Implementations: `SqliteSessionStore` (`@graphorin/store-sqlite`).
|
|
112
|
+
*
|
|
113
|
+
* @stable
|
|
114
|
+
*/
|
|
115
|
+
export interface SessionStoreExt extends SessionStore {
|
|
116
|
+
/** Hard-delete an agent. Used by `AgentRegistry.delete(...)`. */
|
|
117
|
+
deleteAgent(agentId: string): Promise<void>;
|
|
118
|
+
/** List all known agents (including retired ones). */
|
|
119
|
+
listAgents(): Promise<ReadonlyArray<AgentRegistryEntry>>;
|
|
120
|
+
/** Update the status of a workflow attachment. */
|
|
121
|
+
updateWorkflowRunStatus(
|
|
122
|
+
sessionId: string,
|
|
123
|
+
workflowId: string,
|
|
124
|
+
threadId: string,
|
|
125
|
+
status: SessionWorkflowRun['status'],
|
|
126
|
+
): Promise<void>;
|
|
127
|
+
/** Append a session-lifecycle audit row. */
|
|
128
|
+
appendAuditEntry(entry: SessionAuditEntry): Promise<void>;
|
|
129
|
+
/** List recent audit rows for a session, newest-first. */
|
|
130
|
+
listAuditEntries(
|
|
131
|
+
sessionId: string,
|
|
132
|
+
opts?: { readonly limit?: number },
|
|
133
|
+
): Promise<ReadonlyArray<SessionAuditEntry>>;
|
|
134
|
+
/** Delete audit rows older than the supplied epoch ms. */
|
|
135
|
+
pruneAuditEntries(beforeEpochMs: number): Promise<number>;
|
|
136
|
+
/**
|
|
137
|
+
* Hard-delete a session and cascade its session-owned rows - handoffs,
|
|
138
|
+
* workflow-run attachments, and audit entries (RP-6) - **plus the
|
|
139
|
+
* session's content**: its `session_messages` rows (with their FTS and
|
|
140
|
+
* vector index entries) and any episodes scoped to the session
|
|
141
|
+
* (store-01). The cascade also erases the checkpoints of suspended
|
|
142
|
+
* runs (W-005): `workflow_checkpoints` / `workflow_pending_writes`
|
|
143
|
+
* rows for every thread linked to the session, whether through the
|
|
144
|
+
* workflow-run attachment mapping or through the `sessionId`
|
|
145
|
+
* checkpoint metadata the agent runtime stamps on HITL suspends -
|
|
146
|
+
* those snapshots embed the full conversation. After this call the
|
|
147
|
+
* conversation is no longer retrievable through `memory.session.*`
|
|
148
|
+
* search surfaces nor resumable from its checkpoints. A no-op for an
|
|
149
|
+
* unknown id. Custom implementations must honour the same contract in
|
|
150
|
+
* full - leaving any of these surfaces behind defeats erasure.
|
|
151
|
+
*/
|
|
152
|
+
deleteSession(sessionId: string): Promise<void>;
|
|
153
|
+
/**
|
|
154
|
+
* Retention sweep (RP-6): hard-delete (cascade) every session matching the
|
|
155
|
+
* policy. `beforeEpochMs` limits to sessions created before that instant;
|
|
156
|
+
* `closedOnly` limits to closed sessions. With neither, deletes all sessions.
|
|
157
|
+
* Returns the number of sessions deleted.
|
|
158
|
+
*/
|
|
159
|
+
pruneSessions(opts: {
|
|
160
|
+
readonly beforeEpochMs?: number;
|
|
161
|
+
readonly closedOnly?: boolean;
|
|
162
|
+
}): Promise<number>;
|
|
163
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Message } from '../types/message.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pluggable token counter. Implementations live in `@graphorin/provider`
|
|
5
|
+
* (default `JsTiktokenCounter` for OpenAI/compatible, plus per-vendor
|
|
6
|
+
* native counters) and are interchangeable behind this interface.
|
|
7
|
+
*
|
|
8
|
+
* Counters carry a `version` field so that consumers (e.g. the
|
|
9
|
+
* `session_messages.tokenizer_version` cache column) can invalidate stale
|
|
10
|
+
* cached counts when the underlying tokenizer is upgraded.
|
|
11
|
+
*
|
|
12
|
+
* @stable
|
|
13
|
+
*/
|
|
14
|
+
export interface TokenCounter {
|
|
15
|
+
/** Human-readable identifier (`'js-tiktoken@cl100k_base'`, …). */
|
|
16
|
+
readonly id: string;
|
|
17
|
+
/** Tokenizer version string used for cache invalidation. */
|
|
18
|
+
readonly version: string;
|
|
19
|
+
/** Count tokens in a list of `Message`s (system/user/assistant/tool). */
|
|
20
|
+
count(messages: ReadonlyArray<Message>): Promise<number>;
|
|
21
|
+
/** Count tokens in a raw text string. */
|
|
22
|
+
countText(text: string): Promise<number>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import type { MessageContent } from '../types/message.js';
|
|
2
|
+
import type { RunContext } from '../types/run.js';
|
|
3
|
+
import type {
|
|
4
|
+
ContentChunk,
|
|
5
|
+
InboundSanitizationPolicy,
|
|
6
|
+
MemoryGuardTier,
|
|
7
|
+
SandboxPolicy,
|
|
8
|
+
SideEffectClass,
|
|
9
|
+
ToolSource,
|
|
10
|
+
ToolTrustClass,
|
|
11
|
+
TruncationStrategy,
|
|
12
|
+
} from '../types/tool.js';
|
|
13
|
+
import type { ZodLikeSchema } from '../utils/validation.js';
|
|
14
|
+
import type { Logger } from './logger.js';
|
|
15
|
+
import type { ModelHint, ModelSpec } from './preferred-model.js';
|
|
16
|
+
import type { Tracer } from './tracer.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Pluggable function call exposed to an LLM. Concrete `Tool` instances
|
|
20
|
+
* are produced by the `tool({...})` factory in `@graphorin/tools` and
|
|
21
|
+
* by the MCP / Skills loaders. The interface lives in core because every
|
|
22
|
+
* package above the persistence layer (agent runtime, workflow engine,
|
|
23
|
+
* server, sessions, observability, …) carries `Tool[]` references on
|
|
24
|
+
* its public surface.
|
|
25
|
+
*
|
|
26
|
+
* The interface is intentionally minimal - extension fields covered by
|
|
27
|
+
* later phases (per-tool `secretsAllowed` ACL, inbound sanitization
|
|
28
|
+
* policy, result truncation strategy, streaming hint, …) are added
|
|
29
|
+
* **additively** by their owning packages so that v0.1 consumers can
|
|
30
|
+
* type their tool list against `Tool<...>` today without having to
|
|
31
|
+
* worry about future fields.
|
|
32
|
+
*
|
|
33
|
+
* @stable
|
|
34
|
+
*/
|
|
35
|
+
export interface Tool<TInput = unknown, TOutput = unknown, TDeps = unknown> {
|
|
36
|
+
readonly name: string;
|
|
37
|
+
readonly description: string;
|
|
38
|
+
readonly inputSchema: ZodLikeSchema<TInput>;
|
|
39
|
+
readonly outputSchema?: ZodLikeSchema<TOutput>;
|
|
40
|
+
/**
|
|
41
|
+
* Either a static boolean or a predicate consulted at runtime against
|
|
42
|
+
* the realized input. `true` means the runtime suspends the run with a
|
|
43
|
+
* `tool.approval.requested` event before the tool executes.
|
|
44
|
+
*/
|
|
45
|
+
readonly needsApproval?:
|
|
46
|
+
| boolean
|
|
47
|
+
| ((input: TInput, ctx: ToolExecutionContext<TDeps>) => boolean | Promise<boolean>);
|
|
48
|
+
/**
|
|
49
|
+
* Sandbox isolation level. Defaults are picked by
|
|
50
|
+
* `@graphorin/security`. ADVISORY in the default agent build (AG-18):
|
|
51
|
+
* inline `config.tools` closures cannot be serialised out-of-process,
|
|
52
|
+
* so the resolved policy is surfaced on the `tool.execute` span /
|
|
53
|
+
* audit but the tool runs in-process. Real isolation applies to
|
|
54
|
+
* module-loadable (skill / MCP) tools.
|
|
55
|
+
*/
|
|
56
|
+
readonly sandboxPolicy?: SandboxPolicy;
|
|
57
|
+
/** Free-form labels surfaced to operators and to the model. */
|
|
58
|
+
readonly tags?: ReadonlyArray<string>;
|
|
59
|
+
/**
|
|
60
|
+
* Sequential execution mode hints. Tools tagged `'sequential'` are
|
|
61
|
+
* never executed in parallel with each other; the executor serializes
|
|
62
|
+
* them inside the per-step batch.
|
|
63
|
+
*
|
|
64
|
+
* @default `'parallel'`
|
|
65
|
+
*/
|
|
66
|
+
readonly executionMode?: 'parallel' | 'sequential';
|
|
67
|
+
/**
|
|
68
|
+
* Per-tool secrets ACL. Tool execution is wrapped in a scope where
|
|
69
|
+
* `ctx.secrets.require(...)` only resolves keys present here.
|
|
70
|
+
* Empty / undefined means the tool may not request any secret.
|
|
71
|
+
*/
|
|
72
|
+
readonly secretsAllowed?: ReadonlyArray<string>;
|
|
73
|
+
/**
|
|
74
|
+
* Sensitivity ceiling of the tool's input + output payload. Used by
|
|
75
|
+
* the redaction validator to decide whether the result may flow to a
|
|
76
|
+
* given sink (provider / exporter).
|
|
77
|
+
*/
|
|
78
|
+
readonly sensitivity?: import('../types/sensitivity.js').Sensitivity;
|
|
79
|
+
/**
|
|
80
|
+
* Memory-modification guard tier (DEC-153). ACTIVE when the agent is
|
|
81
|
+
* created with `memory` wired (SDF-1): the runtime binds a scope-aware
|
|
82
|
+
* region reader over working memory and the executor snapshots/verifies
|
|
83
|
+
* the region around guarded calls. Without `memory` the guard is
|
|
84
|
+
* skipped and the agent emits a one-time WARN.
|
|
85
|
+
*/
|
|
86
|
+
readonly memoryGuardTier?: MemoryGuardTier;
|
|
87
|
+
/** Inbound prompt-injection sanitization policy. */
|
|
88
|
+
readonly inboundSanitization?: InboundSanitizationPolicy;
|
|
89
|
+
/**
|
|
90
|
+
* When `true`, an inbound-sanitization hit returns `ToolError({ kind:
|
|
91
|
+
* 'inbound_sanitization_blocked' })` instead of forwarding the
|
|
92
|
+
* (sanitized) result. Intended for regulated deployments.
|
|
93
|
+
*
|
|
94
|
+
* @default `false`
|
|
95
|
+
*/
|
|
96
|
+
readonly failClosed?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Defer the tool from the per-step catalogue until the model invokes
|
|
99
|
+
* the built-in `tool_search` to look it up. Tools with deferred
|
|
100
|
+
* loading are not advertised to the model on every step, which keeps
|
|
101
|
+
* the input-token cost bounded for installations with dozens of
|
|
102
|
+
* MCP-derived tools.
|
|
103
|
+
*
|
|
104
|
+
* Naming note (W-127): the snake_case is DELIBERATE - this field
|
|
105
|
+
* mirrors the wire-level `defer_loading` flag of the Anthropic
|
|
106
|
+
* tool-use surface one-to-one, so grep and serialized payloads
|
|
107
|
+
* match. It is the only snake_case field on `Tool` by design, not
|
|
108
|
+
* an oversight.
|
|
109
|
+
*
|
|
110
|
+
* @default `false`
|
|
111
|
+
*/
|
|
112
|
+
readonly defer_loading?: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* Maximum number of tokens the assembled tool result may carry into
|
|
115
|
+
* the conversation history. `0` disables the cap (logs a one-time
|
|
116
|
+
* WARN at registration). Counted against text-shaped output and
|
|
117
|
+
* text-shaped `contentParts` entries; non-text parts pass through.
|
|
118
|
+
*
|
|
119
|
+
* @default `16384`
|
|
120
|
+
*/
|
|
121
|
+
readonly maxResultTokens?: number;
|
|
122
|
+
/** Truncation strategy applied when `maxResultTokens` is exceeded. */
|
|
123
|
+
readonly truncationStrategy?: TruncationStrategy;
|
|
124
|
+
/**
|
|
125
|
+
* Worked examples shown to the model alongside the tool's
|
|
126
|
+
* description. Bounded `[1, 5]` - overflow emits a one-time WARN at
|
|
127
|
+
* registration. Each example's `input` and `output` is validated
|
|
128
|
+
* against the tool's `inputSchema` / `outputSchema`.
|
|
129
|
+
*/
|
|
130
|
+
readonly examples?: ReadonlyArray<ToolExample<TInput, TOutput>>;
|
|
131
|
+
/**
|
|
132
|
+
* Render examples eagerly (every step) regardless of
|
|
133
|
+
* `defer_loading`. When undefined the runtime applies the auto-rule:
|
|
134
|
+
* `defer_loading: true` ⇒ `false`; `defer_loading: false` ⇒ `true`;
|
|
135
|
+
* neither ⇒ `undefined` (the agent runtime decides at assembly time).
|
|
136
|
+
*/
|
|
137
|
+
readonly examplesEagerlyRendered?: boolean;
|
|
138
|
+
/**
|
|
139
|
+
* REQUIRED side-effect classification. v0.1 transition mode emits a
|
|
140
|
+
* one-time WARN per tool name on missing classification and applies
|
|
141
|
+
* the conservative deferred default `'side-effecting'`; v0.2 may
|
|
142
|
+
* promote the WARN to a registration error.
|
|
143
|
+
*/
|
|
144
|
+
readonly sideEffectClass?: SideEffectClass;
|
|
145
|
+
/**
|
|
146
|
+
* Optional callback returning a deterministic dedup key per
|
|
147
|
+
* `(input, ctx)` tuple. REQUIRED-by-WARN for `'side-effecting'` /
|
|
148
|
+
* `'external-stateful'` tools. The framework does not validate
|
|
149
|
+
* determinism - that is the operator's contract.
|
|
150
|
+
*/
|
|
151
|
+
readonly idempotencyKey?: (
|
|
152
|
+
input: TInput,
|
|
153
|
+
ctx: ToolExecutionContext<TDeps>,
|
|
154
|
+
) => string | Promise<string>;
|
|
155
|
+
/**
|
|
156
|
+
* Opt-in flag for streaming-tool execution. The `?: true` typing
|
|
157
|
+
* rejects `streamingHint: false` on purpose - absence is the
|
|
158
|
+
* canonical "non-streaming" signal preserving v0.1 behaviour. When
|
|
159
|
+
* `true`, `Tool.execute(...)` may call `ctx.streamContent(...)` /
|
|
160
|
+
* `ctx.reportProgress(...)` and may return `Promise<void>`.
|
|
161
|
+
*/
|
|
162
|
+
readonly streamingHint?: true;
|
|
163
|
+
/**
|
|
164
|
+
* Per-tool author-time model hint. Either a cost-tier vocabulary
|
|
165
|
+
* literal (`'fast' | 'balanced' | 'smart'`) OR an explicit
|
|
166
|
+
* `ModelSpec` that always wins over the agent-side tier mapping.
|
|
167
|
+
*/
|
|
168
|
+
readonly preferredModel?: ModelHint | ModelSpec;
|
|
169
|
+
/**
|
|
170
|
+
* Execute the tool. Concrete implementations may return either a raw
|
|
171
|
+
* `TOutput` or a `ToolReturn<TOutput>` envelope when extra content
|
|
172
|
+
* parts (images, files, …) need to be appended to the conversation.
|
|
173
|
+
* Streaming-hint tools may also return `void` once the per-chunk
|
|
174
|
+
* buffer has been populated.
|
|
175
|
+
*/
|
|
176
|
+
execute(
|
|
177
|
+
input: TInput,
|
|
178
|
+
ctx: ToolExecutionContext<TDeps>,
|
|
179
|
+
): Promise<TOutput | ToolReturn<TOutput> | undefined>;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Existentially-typed {@link Tool} for collection seams (W-100).
|
|
184
|
+
*
|
|
185
|
+
* `Tool` is invariant in `TInput` (the `needsApproval` /
|
|
186
|
+
* `idempotencyKey` predicate properties are contravariant in it), so a
|
|
187
|
+
* concretely-typed `Tool<{ q: string }, number, D>` is NOT assignable
|
|
188
|
+
* to `Tool<unknown, unknown, D>` - which forced `as unknown as Tool`
|
|
189
|
+
* casts wherever tools are collected. `AnyTool` erases `TInput` /
|
|
190
|
+
* `TOutput` existentially, following the `HandoffEntry` precedent in
|
|
191
|
+
* `@graphorin/agent`.
|
|
192
|
+
*
|
|
193
|
+
* Use it on COLLECTION seams (`createAgent({ tools })`, executor
|
|
194
|
+
* options, registries); implement tools against the typed `Tool` via
|
|
195
|
+
* the `tool({...})` factory.
|
|
196
|
+
*
|
|
197
|
+
* @stable
|
|
198
|
+
*/
|
|
199
|
+
// biome-ignore lint/suspicious/noExplicitAny: existential TInput/TOutput (see above)
|
|
200
|
+
export type AnyTool<TDeps = unknown> = Tool<any, any, TDeps>;
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Worked example for a `Tool`. Type-parameterized on the same generics
|
|
204
|
+
* as `Tool`, so a `ToolExample` for `Tool<{ q: string }, { hits: T[] }>`
|
|
205
|
+
* cannot specify an `input` shape that does not match.
|
|
206
|
+
*
|
|
207
|
+
* @stable
|
|
208
|
+
*/
|
|
209
|
+
export interface ToolExample<TInput = unknown, TOutput = unknown> {
|
|
210
|
+
readonly input: TInput;
|
|
211
|
+
readonly output: TOutput | ToolReturn<TOutput>;
|
|
212
|
+
readonly comment?: string;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Resolved record returned by the `ToolRegistry` getter. Carries every
|
|
217
|
+
* non-public registration-time field downstream layers consume
|
|
218
|
+
* (sanitization, audit, retrieval, side-effect classification,
|
|
219
|
+
* collision resolution, …) so consumers do not have to recompute it.
|
|
220
|
+
*
|
|
221
|
+
* @stable
|
|
222
|
+
*/
|
|
223
|
+
export interface ResolvedTool<TInput = unknown, TOutput = unknown, TDeps = unknown>
|
|
224
|
+
extends Tool<TInput, TOutput, TDeps> {
|
|
225
|
+
readonly __trustClass: ToolTrustClass;
|
|
226
|
+
readonly __source: ToolSource;
|
|
227
|
+
readonly __effectiveDeferLoading: boolean;
|
|
228
|
+
readonly __sideEffectClass: SideEffectClass;
|
|
229
|
+
readonly __hasIdempotencyKey: boolean;
|
|
230
|
+
readonly __streamingHint: boolean;
|
|
231
|
+
readonly __exampleCount: number;
|
|
232
|
+
readonly __preferredModel?: ModelHint | ModelSpec;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Optional return envelope: pairs a typed `output` (passed to the model)
|
|
237
|
+
* with extra `contentParts` that are appended verbatim to the
|
|
238
|
+
* conversation (images, files, audio, …).
|
|
239
|
+
*
|
|
240
|
+
* @stable
|
|
241
|
+
*/
|
|
242
|
+
export interface ToolReturn<TOutput = unknown> {
|
|
243
|
+
/**
|
|
244
|
+
* W-115: envelope brand set by the {@link toolReturn} factory.
|
|
245
|
+
* `Symbol.for`, so duplicate package copies agree. Prefer branding:
|
|
246
|
+
* the structural fallback in the executor is deliberately narrow
|
|
247
|
+
* (own keys within `{output, contentParts, taint}`), and plain data
|
|
248
|
+
* that happens to be exactly `{ output: X }` is ambiguous by
|
|
249
|
+
* construction - brand it (or rename the field) to disambiguate.
|
|
250
|
+
*/
|
|
251
|
+
readonly [TOOL_RETURN_BRAND]?: true;
|
|
252
|
+
readonly output: TOutput;
|
|
253
|
+
readonly contentParts?: ReadonlyArray<MessageContent>;
|
|
254
|
+
/**
|
|
255
|
+
* C6: per-result taint override the data-flow ledger honours when
|
|
256
|
+
* recording this output. Lets a FIRST-PARTY tool whose CONTENT is not
|
|
257
|
+
* first-party (e.g. memory recall returning quarantined /
|
|
258
|
+
* foreign-provenance facts) re-arm the taint ledger, closing the
|
|
259
|
+
* cross-session poisoning leg. Flags only ever WIDEN the derived label
|
|
260
|
+
* (they cannot launder an untrusted tool's output into trusted).
|
|
261
|
+
*/
|
|
262
|
+
readonly taint?: {
|
|
263
|
+
readonly untrusted?: boolean;
|
|
264
|
+
readonly sensitive?: boolean;
|
|
265
|
+
readonly sourceKind?: string;
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* W-115: cross-realm brand for the {@link ToolReturn} envelope
|
|
271
|
+
* (`SECRET_VALUE_BRAND` precedent - `Symbol.for` survives duplicate
|
|
272
|
+
* package copies).
|
|
273
|
+
*
|
|
274
|
+
* @stable
|
|
275
|
+
*/
|
|
276
|
+
export const TOOL_RETURN_BRAND: unique symbol = Symbol.for('graphorin.ToolReturn');
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* W-115: build a BRANDED {@link ToolReturn} envelope. The executor
|
|
280
|
+
* unwraps branded envelopes unconditionally; unbranded objects fall to
|
|
281
|
+
* a deliberately narrow structural sniff (own keys within
|
|
282
|
+
* `{output, contentParts, taint}`), so a tool legitimately returning
|
|
283
|
+
* `{ output, exitCode, stderr }` is no longer silently stripped to its
|
|
284
|
+
* `output` field.
|
|
285
|
+
*
|
|
286
|
+
* @stable
|
|
287
|
+
*/
|
|
288
|
+
export function toolReturn<TOutput>(fields: {
|
|
289
|
+
readonly output: TOutput;
|
|
290
|
+
readonly contentParts?: ReadonlyArray<MessageContent>;
|
|
291
|
+
readonly taint?: ToolReturn<TOutput>['taint'];
|
|
292
|
+
}): ToolReturn<TOutput> {
|
|
293
|
+
return {
|
|
294
|
+
[TOOL_RETURN_BRAND]: true,
|
|
295
|
+
output: fields.output,
|
|
296
|
+
...(fields.contentParts !== undefined ? { contentParts: fields.contentParts } : {}),
|
|
297
|
+
...(fields.taint !== undefined ? { taint: fields.taint } : {}),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* W-115: the ONE guard for the ToolReturn envelope (the executor and
|
|
303
|
+
* the registry example-normalizer both consume it). Brand first; the
|
|
304
|
+
* structural fallback accepts only objects whose OWN enumerable keys
|
|
305
|
+
* all belong to the canonical envelope shape - `{output, exitCode}`
|
|
306
|
+
* style process results pass through intact.
|
|
307
|
+
*
|
|
308
|
+
* @stable
|
|
309
|
+
*/
|
|
310
|
+
export function isToolReturnEnvelope<TOutput = unknown>(
|
|
311
|
+
value: unknown,
|
|
312
|
+
): value is ToolReturn<TOutput> {
|
|
313
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;
|
|
314
|
+
if ((value as Record<PropertyKey, unknown>)[TOOL_RETURN_BRAND] === true) return true;
|
|
315
|
+
if (!Object.hasOwn(value, 'output')) return false;
|
|
316
|
+
for (const key of Object.keys(value)) {
|
|
317
|
+
if (key !== 'output' && key !== 'contentParts' && key !== 'taint') return false;
|
|
318
|
+
}
|
|
319
|
+
return true;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* W-115: `true` when {@link isToolReturnEnvelope} matched WITHOUT the
|
|
324
|
+
* brand - observability for the future deprecation of the structural
|
|
325
|
+
* sniff.
|
|
326
|
+
*
|
|
327
|
+
* @stable
|
|
328
|
+
*/
|
|
329
|
+
export function isUnbrandedToolReturn(value: unknown): boolean {
|
|
330
|
+
return (
|
|
331
|
+
isToolReturnEnvelope(value) &&
|
|
332
|
+
(value as unknown as Record<PropertyKey, unknown>)[TOOL_RETURN_BRAND] !== true
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Per-call execution context handed to `Tool.execute(...)`. Carries the
|
|
338
|
+
* stable `toolCallId`, the parent `RunContext`, an `AbortSignal` tied to
|
|
339
|
+
* the surrounding agent run, structured tracer / logger handles, the
|
|
340
|
+
* streaming progress / content emitters, and a per-call secrets accessor
|
|
341
|
+
* scoped to the tool's `secretsAllowed` ACL.
|
|
342
|
+
*
|
|
343
|
+
* @stable
|
|
344
|
+
*/
|
|
345
|
+
export interface ToolExecutionContext<TDeps = unknown> {
|
|
346
|
+
readonly toolCallId: string;
|
|
347
|
+
readonly runContext: RunContext<TDeps>;
|
|
348
|
+
readonly signal: AbortSignal;
|
|
349
|
+
readonly tracer: Tracer;
|
|
350
|
+
readonly logger: Logger;
|
|
351
|
+
/**
|
|
352
|
+
* Per-call secrets accessor. The accessor enforces the tool's
|
|
353
|
+
* `secretsAllowed` ACL - calling `require(...)` for a key that is
|
|
354
|
+
* not on the allowlist throws `SecretAccessDeniedError`.
|
|
355
|
+
*/
|
|
356
|
+
readonly secrets: ToolSecretsAccessor;
|
|
357
|
+
/**
|
|
358
|
+
* Emit a progress event to subscribers of `agent.stream(...)`. No-op
|
|
359
|
+
* on tools without `streamingHint: true` AND on aborted streams. The
|
|
360
|
+
* counter pair `(current, total?)` is consumer-rendered as a
|
|
361
|
+
* percentage when both fields are present.
|
|
362
|
+
*/
|
|
363
|
+
reportProgress(current: number, total?: number, message?: string): void;
|
|
364
|
+
/**
|
|
365
|
+
* Emit one chunk of content. Concatenated into the tool's assembled
|
|
366
|
+
* `output` per the buffer-becomes-output discipline. No-op on tools
|
|
367
|
+
* without `streamingHint: true` AND on aborted streams.
|
|
368
|
+
*/
|
|
369
|
+
streamContent(chunk: ContentChunk): void;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Per-call secrets accessor surface. Implemented by the executor; the
|
|
374
|
+
* tool author calls `require(...)` to obtain a `SecretValue` wrapper.
|
|
375
|
+
*
|
|
376
|
+
* The accessor is intentionally narrow - the ACL enforcement happens
|
|
377
|
+
* inside `require(...)`, so the tool author never accidentally
|
|
378
|
+
* unwraps a secret outside the tool's permitted set.
|
|
379
|
+
*
|
|
380
|
+
* @stable
|
|
381
|
+
*/
|
|
382
|
+
export interface ToolSecretsAccessor {
|
|
383
|
+
/**
|
|
384
|
+
* Resolve a secret by key. Throws `SecretAccessDeniedError` if the
|
|
385
|
+
* key is not in the tool's `secretsAllowed` allowlist; throws
|
|
386
|
+
* `SecretRequiredError` (or returns `null` when `optional: true`)
|
|
387
|
+
* if the key resolves to no value.
|
|
388
|
+
*/
|
|
389
|
+
require(
|
|
390
|
+
key: string,
|
|
391
|
+
options?: { readonly optional?: false },
|
|
392
|
+
): Promise<import('./secret-value.js').SecretValue>;
|
|
393
|
+
require(
|
|
394
|
+
key: string,
|
|
395
|
+
options: { readonly optional: true },
|
|
396
|
+
): Promise<import('./secret-value.js').SecretValue | null>;
|
|
397
|
+
}
|