Summary
The Node.js example in the CLI -> SDK (inbound) part of
docs/observability/opentelemetry.md
shows how a tool handler can restore the CLI's W3C trace context. It registers the tool like this:
session.registerTool(myTool, async (args, invocation) => { ... });
registerTool is not a member of the public CopilotSession type. Copying the example into a
TypeScript project fails to compile, and if the types are bypassed it throws at runtime.
The plural registerTools() does exist on the class, but it is marked @internal and the package is
built with stripInternal: true, so it is not in the shipped declarations either. There is no public
session method for registering a tool after the session exists, so a reader cannot repair the example
by switching to the plural form - the whole registration step has to be restructured.
The trace-restoration logic in the example is fine. ToolInvocation.traceparent and
ToolInvocation.tracestate are real, public, and correctly delivered to handlers. Only the
registration mechanism is wrong.
Reproduction
Against the published package, no CLI, auth, model call or network needed beyond the install:
mkdir repro && cd repro
printf '{"name":"repro","version":"0.0.0","private":true,"type":"module"}\n' > package.json
npm install @github/copilot-sdk@1.0.8 typescript@5.9.3 @opentelemetry/api@1.9.1
repro.ts - the documented snippet, with only the scaffolding the surrounding docs supply
out-of-band (session, myTool and doWork) added so it is a self-contained compilation unit.
The SDK-facing lines are the documentation's, unchanged:
import type { CopilotSession } from "@github/copilot-sdk";
declare const session: CopilotSession;
declare const myTool: unknown;
declare const doWork: (args: unknown) => Promise<unknown>;
import { propagation, context, trace } from "@opentelemetry/api";
session.registerTool(myTool, async (args, invocation) => {
// Restore the CLI's trace context as the active context
const carrier = {
traceparent: invocation.traceparent,
tracestate: invocation.tracestate,
};
const parentCtx = propagation.extract(context.active(), carrier);
// Create a child span under the CLI's span
const tracer = trace.getTracer("my-app");
return context.with(parentCtx, () =>
tracer.startActiveSpan("my-tool", async (span) => {
try {
const result = await doWork(args);
return result;
} finally {
span.end();
}
})
);
});
$ npx tsc --noEmit --target ES2022 --module NodeNext --moduleResolution NodeNext --strict --skipLibCheck repro.ts
repro.ts(7,9): error TS2339: Property 'registerTool' does not exist on type 'CopilotSession'.
repro.ts(7,37): error TS7006: Parameter 'args' implicitly has an 'any' type.
repro.ts(7,43): error TS7006: Parameter 'invocation' implicitly has an 'any' type.
$ echo $?
2
The two TS7006 errors are a consequence of the same defect: because the method does not resolve,
the compiler has no contextual type for the callback parameters, so the example's own
invocation.traceparent access is not type-checked at all.
Runtime confirmation, no session required:
$ node --input-type=module -e 'import { CopilotSession } from "@github/copilot-sdk";
const p = CopilotSession.prototype;
console.log("registerTool: ", typeof p.registerTool);
console.log("registerTools:", typeof p.registerTools);'
registerTool: undefined
registerTools: function
So a user who bypasses the types gets TypeError: session.registerTool is not a function.
Expected vs actual
- Expected: the documented Node.js example for restoring inbound CLI trace context in a tool
handler compiles and runs.
- Actual: it does not compile (
TS2339), and would throw at runtime. There is no public
session.register*Tool* method to correct it with, so the example has to be restructured to
register the tool through createSession({ tools: [...] }).
Why it was not caught
The block carries a <!-- docs-validate: skip --> marker, so scripts/docs-validation never
compiles it. That marker is reasonable here - the validation setup has no @opentelemetry/api
dependency, so an unskipped block would fail on the missing third-party import rather than on SDK
drift - but it does mean this kind of drift is invisible to CI.
Suggested fix
Attach the handler with defineTool() and register the tool at session creation, which is the
public path and the one used elsewhere in the documentation
(see docs/getting-started.md).
The trace-context logic itself does not need to change. That form compiles clean against the same
published package, and it has the added benefit that args and invocation become contextually
typed, so the trace-context access is actually checked:
$ npx tsc --noEmit --target ES2022 --module NodeNext --moduleResolution NodeNext --strict --skipLibCheck fixed.ts
$ echo $?
0
Environment
@github/copilot-sdk 1.0.8 (npm latest); also reproduces against a build of current main
- TypeScript 5.9.3,
@opentelemetry/api 1.9.1
- Node.js 22, Linux x64
Summary
The Node.js example in the CLI -> SDK (inbound) part of
docs/observability/opentelemetry.mdshows how a tool handler can restore the CLI's W3C trace context. It registers the tool like this:
registerToolis not a member of the publicCopilotSessiontype. Copying the example into aTypeScript project fails to compile, and if the types are bypassed it throws at runtime.
The plural
registerTools()does exist on the class, but it is marked@internaland the package isbuilt with
stripInternal: true, so it is not in the shipped declarations either. There is no publicsession method for registering a tool after the session exists, so a reader cannot repair the example
by switching to the plural form - the whole registration step has to be restructured.
The trace-restoration logic in the example is fine.
ToolInvocation.traceparentandToolInvocation.tracestateare real, public, and correctly delivered to handlers. Only theregistration mechanism is wrong.
Reproduction
Against the published package, no CLI, auth, model call or network needed beyond the install:
repro.ts- the documented snippet, with only the scaffolding the surrounding docs supplyout-of-band (
session,myToolanddoWork) added so it is a self-contained compilation unit.The SDK-facing lines are the documentation's, unchanged:
The two
TS7006errors are a consequence of the same defect: because the method does not resolve,the compiler has no contextual type for the callback parameters, so the example's own
invocation.traceparentaccess is not type-checked at all.Runtime confirmation, no session required:
So a user who bypasses the types gets
TypeError: session.registerTool is not a function.Expected vs actual
handler compiles and runs.
TS2339), and would throw at runtime. There is no publicsession.register*Tool*method to correct it with, so the example has to be restructured toregister the tool through
createSession({ tools: [...] }).Why it was not caught
The block carries a
<!-- docs-validate: skip -->marker, soscripts/docs-validationnevercompiles it. That marker is reasonable here - the validation setup has no
@opentelemetry/apidependency, so an unskipped block would fail on the missing third-party import rather than on SDK
drift - but it does mean this kind of drift is invisible to CI.
Suggested fix
Attach the handler with
defineTool()and register the tool at session creation, which is thepublic path and the one used elsewhere in the documentation
(see
docs/getting-started.md).The trace-context logic itself does not need to change. That form compiles clean against the same
published package, and it has the added benefit that
argsandinvocationbecome contextuallytyped, so the trace-context access is actually checked:
Environment
@github/copilot-sdk1.0.8 (npmlatest); also reproduces against a build of currentmain@opentelemetry/api1.9.1