@runtypelabs/cloudflare-agents 0.1.1
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/LICENSE +21 -0
- package/README.md +92 -0
- package/dist/chunk-BuRpWjxB.mjs +42 -0
- package/dist/chunk-DBVUQIKL.cjs +53 -0
- package/dist/durable-object.cjs +892 -0
- package/dist/durable-object.d.cts +114 -0
- package/dist/durable-object.d.ts +114 -0
- package/dist/durable-object.mjs +891 -0
- package/dist/index.cjs +344 -0
- package/dist/index.d.cts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.mjs +339 -0
- package/dist/observer-DnEMHl8z.d.cts +121 -0
- package/dist/observer-DnEMHl8z.d.ts +121 -0
- package/package.json +56 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Runtype, Inc
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Cloudflare Agents telemetry for Runtype
|
|
2
|
+
|
|
3
|
+
Application-owned run reporting for ordinary Cloudflare Agents SDK chat turns
|
|
4
|
+
and automatic server-side tools. This package is under beta acceptance testing;
|
|
5
|
+
the framework onboarding chip remains gated until deployed verification passes.
|
|
6
|
+
|
|
7
|
+
## Integration
|
|
8
|
+
|
|
9
|
+
Use `observeStreamTextOptions` around the options passed to the application's
|
|
10
|
+
single AI SDK `streamText` call. Return the existing UI response stream without
|
|
11
|
+
adding another consumer. `createDurableObserver` bridges those callbacks to the
|
|
12
|
+
`RuntypeOtlpOutbox` companion Durable Object from the `/durable-object` export.
|
|
13
|
+
|
|
14
|
+
The companion owns telemetry session state, retry storage, and its alarm. The
|
|
15
|
+
application Agent's alarm is unchanged. Register a separate SQLite-backed DO
|
|
16
|
+
binding and migration for the companion, then configure these server-side
|
|
17
|
+
bindings:
|
|
18
|
+
|
|
19
|
+
- `RUNTYPE_AGENT_ID`: the destination Runtype agent.
|
|
20
|
+
- `RUNTYPE_API_KEY`: a telemetry-write key stored as a Worker secret.
|
|
21
|
+
- `RUNTYPE_BASE_URL`: the API origin, for example `https://api.runtype-staging.com`.
|
|
22
|
+
|
|
23
|
+
The application supplies a stable submission ID and separate conversation ID.
|
|
24
|
+
Use a new submission ID for a new user request or explicit rerun. Reuse it for
|
|
25
|
+
transport retries. Reconnecting to observe a response does not start another
|
|
26
|
+
run. Snapshot identity from the accepted application request, rather than
|
|
27
|
+
deriving it from a native Cloudflare trace or the latest conversation message.
|
|
28
|
+
|
|
29
|
+
The initial compatibility fixture pins `agents@0.22.0`,
|
|
30
|
+
`@cloudflare/ai-chat@0.11.0`, and `ai@6.0.277`. The AI SDK callbacks used here
|
|
31
|
+
include experimental APIs, so this package pins its AI SDK peer dependency.
|
|
32
|
+
Upgrade only with the acceptance fixture and callback-contract tests.
|
|
33
|
+
|
|
34
|
+
## Content recording
|
|
35
|
+
|
|
36
|
+
All content categories are disabled by default:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
const content = {
|
|
40
|
+
messages: true,
|
|
41
|
+
instructions: true,
|
|
42
|
+
tools: true,
|
|
43
|
+
errors: false,
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Instructions follow `messages` when `instructions` is omitted. Set
|
|
48
|
+
`instructions: false` to record conversation messages without system/developer
|
|
49
|
+
instructions. Per-call effective instructions are captured after `prepareStep`,
|
|
50
|
+
including role and order. Provider-internal prompts are not observable.
|
|
51
|
+
|
|
52
|
+
`tools` controls tool payloads, not structural call identity or duration.
|
|
53
|
+
Error messages are a separate opt-in content boundary. A `redact(kind, value)`
|
|
54
|
+
callback receives a detached JSON value and may return a replacement or
|
|
55
|
+
`undefined` to omit it. Redaction runs before storage or transmission. If it
|
|
56
|
+
throws, the content is omitted and only a bounded diagnostic code is emitted.
|
|
57
|
+
|
|
58
|
+
Content fields are bounded to 32 KiB of serialized UTF-8 after redaction;
|
|
59
|
+
oversized values are omitted whole. The adapter never stores its API key in
|
|
60
|
+
the outbox payload. Application-supplied identifiers and model/tool names must
|
|
61
|
+
be safe metadata, rather than user content or credentials.
|
|
62
|
+
|
|
63
|
+
## Lifecycle and delivery
|
|
64
|
+
|
|
65
|
+
The adapter assigns one OTLP trace to each accepted submission. Ended model and
|
|
66
|
+
tool spans are immutable. A reserved root span is emitted once after an
|
|
67
|
+
observed terminal outcome; it is never sent unfinished and later replaced.
|
|
68
|
+
Pending serialized batches and session changes commit together in the companion
|
|
69
|
+
before a successful observation receipt is returned.
|
|
70
|
+
|
|
71
|
+
Remote delivery is at least once. A lost acknowledgement retries the same
|
|
72
|
+
serialized facts, allowing Runtype's durable ingest to deduplicate them. Retry
|
|
73
|
+
and expiry diagnostics are available through the companion's `diagnostics()`
|
|
74
|
+
method. Persistence or delivery failure does not intentionally fail the model
|
|
75
|
+
response; provide an observer diagnostic callback and inspect companion health.
|
|
76
|
+
|
|
77
|
+
Recovery delivers already-persisted telemetry after restart. It cannot resume
|
|
78
|
+
agent execution or recreate a terminal observation that never reached storage.
|
|
79
|
+
An observed cancellation is reported with the canonical `cancelled` stop
|
|
80
|
+
reason. No terminal observation means completion is unknown.
|
|
81
|
+
|
|
82
|
+
## Beta exclusions
|
|
83
|
+
|
|
84
|
+
Human approval continuations, client-executed tools, automatic subagent trees,
|
|
85
|
+
native-trace association, and hosted approval/resume controls are unsupported.
|
|
86
|
+
Continuations and unresolved tool calls emit an unsupported observation instead
|
|
87
|
+
of a fabricated successful terminal. Native Cloudflare tracing can remain
|
|
88
|
+
enabled for infrastructure diagnostics; it is not the adapter's export path.
|
|
89
|
+
|
|
90
|
+
The deployable acceptance kit lives in
|
|
91
|
+
`apps/api/tests/fixtures/cloudflare-adapter-capture` in the Core repository.
|
|
92
|
+
Its package-boundary test uses a packed copy of this package, not source aliases.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//#region src/identity.ts
|
|
2
|
+
function requireIdentity(value) {
|
|
3
|
+
if (typeof value !== "string" || value.trim().length === 0 || new TextEncoder().encode(value).length > 256) throw new Error("Adapter identity must be a nonempty string of at most 256 UTF-8 bytes");
|
|
4
|
+
}
|
|
5
|
+
function requireOperationId(value) {
|
|
6
|
+
if (typeof value !== "string" || value.trim().length === 0 || new TextEncoder().encode(value).length > 2053) throw new Error("Adapter operation id must be a nonempty string of at most 2053 UTF-8 bytes");
|
|
7
|
+
}
|
|
8
|
+
async function digest(parts) {
|
|
9
|
+
const bytes = new TextEncoder().encode(JSON.stringify(parts));
|
|
10
|
+
const hash = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
|
|
11
|
+
return Array.from(hash, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
12
|
+
}
|
|
13
|
+
/** Stable producer identity, independent of native traces and export partitioning. */
|
|
14
|
+
async function createRunIdentity(input) {
|
|
15
|
+
requireIdentity(input.agentId);
|
|
16
|
+
requireIdentity(input.submissionId);
|
|
17
|
+
requireIdentity(input.conversationId);
|
|
18
|
+
const traceId = (await digest([
|
|
19
|
+
"runtype-cloudflare-run-v1",
|
|
20
|
+
input.agentId,
|
|
21
|
+
input.submissionId
|
|
22
|
+
])).slice(0, 32);
|
|
23
|
+
const rootSpanId = await operationSpanId(traceId, "root");
|
|
24
|
+
return {
|
|
25
|
+
...input,
|
|
26
|
+
traceId,
|
|
27
|
+
rootSpanId
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** The same operation is encoded with the same span ID on every delivery attempt. */
|
|
31
|
+
async function operationSpanId(traceId, operationId) {
|
|
32
|
+
if (!/^[0-9a-f]{32}$/.test(traceId) || /^0+$/.test(traceId)) throw new Error("Invalid adapter trace identity");
|
|
33
|
+
requireOperationId(operationId);
|
|
34
|
+
return (await digest([
|
|
35
|
+
"runtype-cloudflare-span-v1",
|
|
36
|
+
traceId,
|
|
37
|
+
operationId
|
|
38
|
+
])).slice(0, 16);
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { operationSpanId as n, createRunIdentity as t };
|
|
42
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
//#region src/identity.ts
|
|
2
|
+
function requireIdentity(value) {
|
|
3
|
+
if (typeof value !== "string" || value.trim().length === 0 || new TextEncoder().encode(value).length > 256) throw new Error("Adapter identity must be a nonempty string of at most 256 UTF-8 bytes");
|
|
4
|
+
}
|
|
5
|
+
function requireOperationId(value) {
|
|
6
|
+
if (typeof value !== "string" || value.trim().length === 0 || new TextEncoder().encode(value).length > 2053) throw new Error("Adapter operation id must be a nonempty string of at most 2053 UTF-8 bytes");
|
|
7
|
+
}
|
|
8
|
+
async function digest(parts) {
|
|
9
|
+
const bytes = new TextEncoder().encode(JSON.stringify(parts));
|
|
10
|
+
const hash = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
|
|
11
|
+
return Array.from(hash, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
12
|
+
}
|
|
13
|
+
/** Stable producer identity, independent of native traces and export partitioning. */
|
|
14
|
+
async function createRunIdentity(input) {
|
|
15
|
+
requireIdentity(input.agentId);
|
|
16
|
+
requireIdentity(input.submissionId);
|
|
17
|
+
requireIdentity(input.conversationId);
|
|
18
|
+
const traceId = (await digest([
|
|
19
|
+
"runtype-cloudflare-run-v1",
|
|
20
|
+
input.agentId,
|
|
21
|
+
input.submissionId
|
|
22
|
+
])).slice(0, 32);
|
|
23
|
+
const rootSpanId = await operationSpanId(traceId, "root");
|
|
24
|
+
return {
|
|
25
|
+
...input,
|
|
26
|
+
traceId,
|
|
27
|
+
rootSpanId
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** The same operation is encoded with the same span ID on every delivery attempt. */
|
|
31
|
+
async function operationSpanId(traceId, operationId) {
|
|
32
|
+
if (!/^[0-9a-f]{32}$/.test(traceId) || /^0+$/.test(traceId)) throw new Error("Invalid adapter trace identity");
|
|
33
|
+
requireOperationId(operationId);
|
|
34
|
+
return (await digest([
|
|
35
|
+
"runtype-cloudflare-span-v1",
|
|
36
|
+
traceId,
|
|
37
|
+
operationId
|
|
38
|
+
])).slice(0, 16);
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
Object.defineProperty(exports, "createRunIdentity", {
|
|
42
|
+
enumerable: true,
|
|
43
|
+
get: function() {
|
|
44
|
+
return createRunIdentity;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
Object.defineProperty(exports, "operationSpanId", {
|
|
48
|
+
enumerable: true,
|
|
49
|
+
get: function() {
|
|
50
|
+
return operationSpanId;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|