Skip to content

docs(workflow): add runnable TypeScript ports of the graph-workflow doc snippets - #634

Merged
ScottMansfield merged 8 commits into
mainfrom
docs/workflow-samples
Aug 12, 2026
Merged

docs(workflow): add runnable TypeScript ports of the graph-workflow doc snippets#634
ScottMansfield merged 8 commits into
mainfrom
docs/workflow-samples

Conversation

@kalenkevich

Copy link
Copy Markdown
Collaborator

Link to Issue or Description of Change

2. Or, if no issue exists, describe the change:

Problem:

The graph-workflow snippets on adk.dev/graphs are
Python-only, and they are fragments: they reference helpers they never define
(condition(), task_A_node, …), so they cannot be run as written even in
Python. A TypeScript reader has nothing to copy from, and no way to confirm a
concept behaves the way the page claims.

Solution:

26 runnable ports, one directory per snippet, grouped by the docs page it comes
from so a sample directory maps 1:1 to a section anchor on adk.dev.

Page Samples
graphs/ get_started, process_pipeline
routes/ sequence, branches, function_node, fan_out_join, loop_escalation, nested_workflow
data_handling/ node_output, routing_output, schemas, session_state, structured_access, structured_output, user_message
dynamic/ get_started, nodes, custom_run_ids, data_handling, human_input, loop_route, parallel_route, sequence_route
human_input/ get_started, initial_prompt, payload_and_schema

Each port fills in the undefined helpers with the smallest plausible
implementation and says so in its header comment. Where TypeScript genuinely
diverges from the Python API, the file comments say why rather than leaving a
reader to guess — for example Python's Event(message=...) has no TS
equivalent (a user-facing message is the event's content, which unlike
output is not handed to the next node), and a graph's validating schema
belongs on the node wrapping an agent rather than on the agent itself.

18 of the 26 run with no API key, so routing, loops, fan-out/join, dynamic
dispatch and human-in-the-loop are all explorable offline. The remaining 8 call
a live model and say so in their header.

samples/workflows/README.md covers how to run them, the offline/online split,
and how to script a multi-turn run for the HITL samples.

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.

No new unit tests: these are documentation samples, and the behavior they
demonstrate is already covered by tests/integration/workflows/ (which holds
vendored copies of an earlier, separate sample set for exactly that reason).
The existing suite is unaffected by this PR — it adds only files under
samples/.

Worth noting for reviewers: samples/** is covered by the repo's root
tsconfig.json (verified — tsc --listFiles picks up all 26), so these compile
in CI rather than rotting silently.

npx tsc --noEmit      -> 0 errors
npx eslint samples    -> clean
npx prettier --check "samples/**/*.{ts,md}" -> all files use Prettier code style

Manual End-to-End (E2E) Tests:

All 26 were verified to construct, and all 18 offline ones were verified to
actually run and produce output — not just compile.

Construction (a workflow validates its graph in its constructor, so a bad graph
fails at load):

data_handling    expected=7 loaded=7
dynamic          expected=8 loaded=8
graphs           expected=2 loaded=2
human_input      expected=3 loaded=3
routes           expected=6 loaded=6

Running each offline sample through api_server with the message hello world:

  data_handling/node_output            OK 5 ev
  data_handling/routing_output         OK 3 ev
  data_handling/session_state          OK 4 ev
  data_handling/structured_output      OK 3 ev
  data_handling/user_message           OK 5 ev
  dynamic/custom_run_ids               OK 5 ev
  dynamic/get_started                  OK 3 ev
  dynamic/human_input                  OK 1 ev  [pauses for input]
  dynamic/nodes                        OK 5 ev
  dynamic/parallel_route               OK 4 ev
  human_input/get_started              OK 1 ev  [pauses for input]
  human_input/initial_prompt           OK 1 ev  [pauses for input]
  human_input/payload_and_schema       OK 2 ev  [pauses for input]
  routes/fan_out_join                  OK 6 ev
  routes/function_node                 OK 3 ev
  routes/loop_escalation               OK 8 ev
  routes/nested_workflow               OK 5 ev
  routes/sequence                      OK 4 ev

The four [pauses for input] results are correct — those samples are meant to
stop and wait for a human on turn 1. Driven to completion interactively:

$ npm run sample -- samples/workflows/human_input/get_started/agent.ts
[user]: start
--- [step1] is waiting for your input ---
Enter a number:
[user]: 21
[step2]: 42
[root_agent]: 42

And routes/loop_escalation, confirming the back-edge loops and then exits:

seed_draft -> critic(REVISE) -> refine -> critic(REVISE) -> refine -> critic(DONE) -> finalize
final: "Approved after 3 bullets: ..."

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules.

Additional context

Relationship to #595. An earlier, different sample set — a flat
samples/workflows/<name>/ layout ported from adk-python's
contributing/samples/workflows — was dropped from #595 before it merged, and
its integration tests kept self-contained vendored copies (the "Vendored copy of
samples/workflows/…" headers in tests/integration/workflows/ still point at
those now-absent paths).

This PR is not a revert of that. It is a different set with a different
purpose: those mirrored the Python contributing samples, these mirror the
public docs pages, and the layout is nested by docs page rather than flat. If
the reason for dropping the earlier set applies here too, say so and I'll close
this — but the doc-alignment rationale seemed distinct enough to be worth
proposing separately.

Discovery caveat. adk web / api_server scan one directory level only, so
they must be pointed at a category directory, not at samples/workflows:

adk web samples/workflows          # -> []  (nested one level too deep)
adk web samples/workflows/routes   # -> 6 apps

Individual samples run fine by path via npm run sample -- <path>, which is
what the README documents. Flattening the layout would fix the adk web case at
the cost of the 1:1 mapping to docs sections; happy to change it if reviewers
prefer that trade.

@kalenkevich kalenkevich self-assigned this Aug 6, 2026

@AmaadMartin AmaadMartin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Good idea, and the framing in the README is right: the published snippets are fragments that cannot run even in Python, so a reader has had nothing to copy.

I checked the two things most likely to be quietly wrong. All twelve symbols the samples import resolve from the package root — worth checking, because common.ts:345 re-exports the workflow API as a hand-maintained subset with a "keep this in sync" comment, so a symbol existing in workflow/index.ts does not mean it is public. And dynamic/human_input already sets rerunOnResume: true on both the parent and the child, so the semantics change in your #635 does not invalidate it.

One comment below. I spot-checked API usage rather than reading all 26 files line by line — which is the reason that comment matters.

Comment on lines +19 to +22
```bash
npm run build # builds @google/adk (and the CLI); needed once / after changes
npm run sample -- samples/workflows/routes/sequence/agent.ts
```

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit, and the one thing I would fix before merging. Nothing in CI type-checks these files.

npm run build            # builds @google/adk (and the CLI); needed once / after changes

That builds the workspaces, which are core, dev and integrationssamples/ is not one, and no tsconfig names it. validation.yaml runs secretlint, build, tests, lint, format and docs. Lint reaches these files, but eslint.config.js uses tseslint.configs.recommended, which is not type-aware, so a wrong signature, a renamed type or a removed export passes it.

The result is 2129 lines that back public documentation pages and that no job compiles. The workflow API is @experimental, and #635, #636 and #637 are all changing it right now.

The script already exists — "ts:check": "tsc --noEmit" — and the root tsconfig names no include, so it picks these up. Adding one step to validation.yaml covers it:

- name: Type check
  run: npm run ts:check

I have not confirmed ts:check passes repo-wide today, so it may need a scoped tsconfig instead.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch — fixed in c8ff7b4.

You were right to hedge on ts:check: it does not pass repo-wide today. 288 errors in 44 files, all of them test files (core/test/**, tests/integration/**) — src/ and samples/ are clean. So a bare npm run ts:check step would have gone red on arrival, and fixing those 288 is not this PR's business.

Scoped it instead, following the same extends-the-root pattern core, dev and integrations already use:

// samples/tsconfig.json
{
  "extends": "../tsconfig.json",
  "include": ["**/*.ts"],
  "exclude": ["node_modules"]
}
"ts:check:samples": "tsc --noEmit -p samples"
- name: Type check samples
  run: npm run ts:check:samples

Placed after Build packages, since @google/adk resolves to core/dist/types.

Verified it is not vacuously green: tsc -p samples --listFiles picks up all 26 files, and a deliberately broken annotation in routes/sequence/agent.ts fails the step as expected.

Also added a note to the README under Running, since the build command there was the thing that implied coverage that did not exist:

samples/ is not an npm workspace, so npm run build does not compile it. It has its own samples/tsconfig.json and is type-checked separately, in CI and locally.

The repo-wide ts:check failure seems worth its own issue — happy to file it if it is not already tracked.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Rebased onto current main (cf6e7ed) — no content change, but it matters for
the red check, which was not this branch.

run-tests (macos-latest) failed with seven Cannot read properties of undefined in tests/integration/workflows/**, none of them files this PR
touches. That was #616 invalidating every recorded fixture in the sample
harness; #647 fixed it on main at 18:07, and this PR's CI ran at 15:11. The
merge commit predated the fix, and re-running the job would have reused the same
merge SHA, so the rebase is what picks it up.

Re-verified on the rebased branch, so the type check is doing real work against
the API as it stands after #635 and #637:

npm run ts:check:samples   -> 0 errors
npx vitest run --project integration tests/integration/workflows
                           -> 37 files, 84 tests, all passing
npm run lint / format:check -> clean

And behaviorally, not just at the type level: all 26 samples construct, and the
18 offline ones still run end-to-end through a real InMemoryRunner with the
same event counts as before the rebase — worth checking specifically, since #637
changed how a finished run rehydrates and #635 changed dynamic HITL resume, and
the HITL samples (human_input/*, dynamic/human_input) lean on both. They
still pause on turn 1 as intended.

One thing to flag for whoever reviews the CI step: ts:check:samples is scoped
because the repo-wide ts:check does not pass today. I have a separate branch
that fixes those 288 errors and wires the repo-wide check in; if that lands,
ts:check:samples becomes redundant and should be folded into it rather than
kept alongside.

@kalenkevich kalenkevich mentioned this pull request Aug 10, 2026
9 tasks
@kalenkevich
kalenkevich force-pushed the docs/workflow-samples branch from 27e7c18 to cf6e7ed Compare August 12, 2026 01:34
kalenkevich added a commit that referenced this pull request Aug 12, 2026
Fix the types the tests were working around, rather than the tests:

- `ListSessionsRequest.userId` was declared required while all three
  implementations branch on its absence, so the test that covers the
  no-filter path needed a cast. It is optional now; the cast and its
  comment are gone and those call sites are back to their original form.
  `InMemorySessionService` was the one site indexing on it, and now lists
  across users when it is omitted, matching the other two.
- `InMemoryPolicyEngine.evaluate()` declared no parameter, so the
  exported class could not be called through `BasePolicyEngine`.

Give the root config an `exclude`: the one it inherits from gts resolves
relative to gts, so the check was also compiling the 270 `dist/**/*.d.ts`
files the preceding build step emits.

Pin the workflow's actions to commit SHAs, matching #634, so zizmor's
unpinned-uses check passes on the file this PR touches.

@AmaadMartin AmaadMartin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I read the whole diff and checked every sample against core/src/workflow and the adk.dev pages at cf6e7ed. The ports are faithful and the API use is correct: node, FunctionNode, JoinNode, RequestInput, ctx.runNode and the {Class.field} / <Class.field from source_node> syntax all match the source. Two header comments state framework behaviour that the code contradicts, and the CI change overlaps PR #648.

Comment on lines +32 to +38
* 2. The `rerun_on_resume=False` leaf ("complete with the human's reply as my
* output") is implemented for STATIC GRAPH nodes only — see
* samples/workflows/human_input/get_started, where that handoff is exactly
* what makes the two-node pattern work. A dynamic `ctx.runNode` child is
* always re-run instead, so the leaf here uses the re-entry form: a stable
* `interruptId`, and a `ctx.resumeInputs[id]` lookup that returns the reply
* on the second pass.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not a nit. This claim is stale.

 * 2. The `rerun_on_resume=False` leaf ("complete with the human's reply as my
 *    output") is implemented for STATIC GRAPH nodes only

PR #635 added that handoff for dynamic children, and it is in this branch: core/src/workflow/dynamic_node_scheduler.ts:79 says it "Mirrors the static-graph handoff in Workflow.scheduleNode". A ctx.runNode child with rerunOnResume: false now completes with the human's reply as its output. The sample still runs, because the leaf sets rerunOnResume: true. Fix the reason, or port the doc's rerun_on_resume=False leaf. README.md:152 repeats the claim.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed, and you were right that it was not a nit — the leaf now ports the doc's
rerun_on_resume=False form directly rather than working around it.

resumeHandoff (dynamic_node_scheduler.ts:134) does exactly what the static
graph does, so the whole re-entry apparatus was unnecessary: the stable
APPROVAL_INTERRUPT_ID, the ctx.resumeInputs[id] lookup and the two-pass
branch are gone, and the leaf is the one-liner the snippet shows.

const getUserApproval = node(
  () => new RequestInput({message: 'Please approve this request (Yes/No)'}),
  {name: 'get_user_approval', rerunOnResume: false},
);

Checked by running it, not by reading the scheduler: turn 1 pauses, turn 2
resolves yes to Approved, and the leaf body does not run a second time.
README.md:152 is gone with the rest of that section — see the reply on the
session-state thread, the third claim in the same family turned out to be
stale too.

Comment on lines +23 to +25
* Caution: a node may emit only ONE event carrying `output` per execution. You
* can yield as many events as you like, but only one of them may set `output` —
* the rest should carry `content` (a display message) instead.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Not a nit. The one-output rule is not real.

 * Caution: a node may emit only ONE event carrying `output` per execution.

core/src/workflow/node_runner.ts:234 runs child.output = event.output for every event, so the LAST event that sets output wins. Nothing throws. The Python page states a third behaviour: each yield "adds to a list of data objects on the Event". Please state the real rule, and add this to the README list of Python-to-TypeScript differences. README.md:87 repeats "one output per node".

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed. You are right on the mechanism, and the Python page is worse than
either of us said — it gives two accounts of this, and they contradict each
other:

  • Node output: each yield "adds to a list of data objects on the Event".
  • Passing structured data (Caution): two yields with Event.output "results in
    a runtime error".

Neither happens here. Confirmed by running a node that yields two output
events: both are emitted, nothing throws, and the successor is handed the
second — node_runner.ts:234 last-write-wins, as you said.

The comment now states that, names both Python claims so a reader coming from
the page is not left wondering which applies, and keeps the advice (emit once,
put progress on content) as advice rather than as a rule the framework
enforces. Added to the README's Python-to-TypeScript differences list, and
README.md:87 no longer says "one output per node".

Comment on lines +161 to +166
The `tests/integration/workflows/*/agent.ts` files are a second, larger set of
workflow examples — TypeScript ports of Python's
[`contributing/samples/workflows`](https://github.com/google/adk-python/tree/main/contributing/samples/workflows),
each paired with a record/replay integration test. They cover surface these
docs snippets do not: retries, parallel workers, auth (API key and OAuth),
node-as-tool, `task` mode, and multi-trigger nodes.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit. No check runs these 26 samples.

eslint "**/*.ts", prettier "**/*.ts", scripts/check_license.sh and the new tsc step all read samples/, so a syntax, style, license or type error fails CI. Nothing executes a workflow, so a graph that stops validating in its constructor stays green.

The sibling set named here solves that: each tests/integration/workflows/*/ pairs an agent with a record/replay test, and npm run record:samples re-records them. Please consider the same for the 18 offline samples.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done — tests/integration/docs_samples/docs_samples_test.ts.

All 26 are constructed, which is the case you named: a WorkflowAgent
validates its graph in its constructor, so that catches a sample that stops
loading. The 18 offline ones are also run end-to-end through a real
InMemoryRunner, reusing your _harness/sample_harness.ts in offline mode —
which has the useful side effect that an "offline" sample that starts calling a
model throws on the empty response set instead of reaching the network.

One table-driven file rather than 18 pairs. The sibling set needs a file each
because each one asserts something specific about its own graph and carries a
fixture; these need no fixtures, and the property being checked is the same for
every one of them, so 18 near-identical files would be copies rather than
tests. A guard case asserts the table matches the directories on disk, so a new
sample fails until it is classified — the "silently uncovered" hole is closed
by that, not by the file count.

The 8 model-backed samples are constructed only. Driving them means a
checked-in fixture each, and what they exercise beyond the sibling set is
prompt wording rather than graph shape. Say the word if you would rather have
them recorded too.

Checked against all three failures it should catch, since a green new test
proves nothing on its own:

  • duplicate node name in routes/sequence -> that sample's case fails in
    validateDuplicateNodeNames
  • an unregistered new sample directory -> the coverage guard fails
  • an LlmAgent spliced into an offline graph -> fails on the missing fixture

npm run lint, format:check, check_license.sh, ts:check:samples and the
full integration project (74 files, 206 tests) all pass with it.


const initStateNode = node(
(ctx: NodeContext, nodeInput: string) => {
ctx.state.set('topic', String(nodeInput).trim());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit, optional. The samples split on input coercion.

11 files wrap the input with String(nodeInput); 8 call nodeInput.trim() or .toUpperCase() on a parameter already typed string. Please pick one.

extractWorkflowInput (core/src/workflow/workflow_agent.ts:187) returns a string for a text message, and the raw Content for anything else. So String() turns a non-text turn into "[object Object]", and the bare call throws a clear error.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Picked the bare call, and dropped the 11 String(...) wrappers.

Your reasoning decided it: both forms are unsound for a non-text turn, since
extractWorkflowInput returns the raw Content then, but they fail
differently. String() produces "[object Object]" and carries it through the
graph; the bare call throws where the mistake is. For a sample, failing at the
point of the wrong assumption is worth more than limping on.

Kept where the value genuinely is untyped, which is not the same case —
ctx.runNode(...).output and a ctx.resumeInputs[id] reply are both
unknown, so those two coerce explicitly at the point of use.

Also wrote the assumption down, since it is invisible otherwise: the README's
differences list now says a workflow's input is a string only for a text-only
turn, that these entry nodes therefore fail loudly on anything else, and to
take a Content or unknown if you need to accept one.

This thread also turned up a third stale claim, on the same theme as your other
two — the session-state gotcha. It described the bug #636 fixed yesterday
(fcc6c1e), so the sample was routing attempts along its edges to dodge a
race that no longer exists. It is a state key again, initialized by one node,
incremented by a second, read by a third — which is what the Python snippet
does and what its inline comment says it prints. Checked from both sides:
reverting #636's node_context.ts makes the third node read 0, and with it
in place the sample prints attempts state: 1. The README gotchas section is
gone entirely now.

Comment thread package.json
"clean:all": "rm package-lock.json && rm -rf ./node_modules && npm run clean:all --workspaces",
"rebuild": "npm run clean:all && npm install && npm run build",
"ts:check": "tsc --noEmit",
"ts:check:samples": "tsc --noEmit -p samples",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit. I retract my earlier framing here, and I am sorry for the round trip.

I wrote that ts:check:samples duplicates the root ts:check. You already answered that: the repo-wide check fails today with 288 errors in test files, the scoped config was my own fallback suggestion, and your latest note says #648 should absorb this script. Nothing to change.

One point stands. #648 edits the same validation.yaml lines this PR edits (permissions, persist-credentials, the SHA pins), so whichever lands second needs a manual rebase.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No apology needed — and thanks for the #648 heads-up, which was the part that
mattered.

Nothing changed here. On the collision: your 80ee6fc landed on this branch
while I was working, so the rebase kept it and my four commits sit on top of
it. That commit already handles the paths half of the #648 interaction —
samples/tsconfig.json now resets "paths": {} so the samples keep resolving
@google/adk through node_modules once #648 aliases it to core/src at the
root. The validation.yaml half (permissions, persist-credentials, the SHA
pins) still needs a manual rebase for whichever lands second, as you said.

Also rebased onto current main (fcc6c1e) while here, which is what surfaced
#636 and the third stale claim — see the session-state thread.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Following up here since this is the thread about the #648 collision — it landed
while I was working, so this branch is rebased onto it (82c7b60) and the
conflict is resolved.

Three things fell out of it:

The validation.yaml conflict resolved as both steps rather than one:
ts:check for the repo, ts:check:samples for the samples. Reasoning below.

Your zizmor commit dropped outpermissions: contents: read,
persist-credentials: false and the three SHA pins all came in with #648, so
git dropped that patch as already upstream. Nothing lost.

#648 also created a quieter problem, now fixed. The root config names no
include and excludes only node_modules and **/dist, so the repo-wide
tsc --noEmit picked up all 26 sample files — and resolved their
@google/adk imports through the root paths aliases, against core/src.
That is exactly the resolution 80ee6fc added "paths": {} to prevent, so the
two checks were running over the same files with opposite resolutions, and the
wrong one would have won any disagreement. samples is excluded from the root
config now, leaving one owner.

Verified on both sides rather than assumed: tsc --noEmit --listFiles reports
0 files under samples/ and still passes, and tsc -p samples --listFiles
reports all 26 and resolves @google/adk to core/dist/types/index.d.ts.

So ts:check:samples is not redundant after #648 — it is the only check that
sees these files the way a user's project would. Happy to fold it into
ts:check instead if you would rather have one step, but that means the
samples get checked against workspace sources, which 80ee6fc argues against.

@kalenkevich
kalenkevich force-pushed the docs/workflow-samples branch from 80ee6fc to 0a0238e Compare August 12, 2026 02:12
kalenkevich added a commit that referenced this pull request Aug 12, 2026
* ci: type-check the repo in CI

`npm run ts:check` (`tsc --noEmit`) existed but was wired into nothing.
validation.yaml runs secretlint, build, tests, lint, format and docs; the
build only compiles the three workspaces' `src/`, and `eslint.config.js` uses
tseslint's non-type-aware recommended config. So no job compiled a single test
file, and `tsc --noEmit` had drifted to 287 errors across 44 files.

Two thirds of those were one bug, not 200. A test that imports both
`@google/adk` and `../../src/...` got two distinct identities for the same
class, because vitest aliases `@google/adk` to `core/src` while tsc resolved it
to the built `core/dist/types`. Mirroring the vitest aliases as `paths` in the
root tsconfig drops it to 164. The per-workspace configs reset `paths` to `{}`
so a published build still resolves through node_modules -- verified by
diffing the emitted declarations before and after: byte-identical.

The remaining errors were real drift in test files, fixed rather than
suppressed -- no `@ts-ignore`, no `as any`, and 22 casts removed against 15
added. The recurring ones:

- `LiveServerMessage` is a class with `text`/`data` getters, so the ~45 object
  literals pushed at it were never assignable. Added one shared
  `liveServerMessage()` helper that builds a real instance.
- String literals where a genai enum member is required (`Modality`,
  `BlockedReason`, `Type`, `Language`, `FileContentEncoding`). The enum values
  are the same strings, so the assertions are untouched.
- Mocks whose signatures had drifted from the type they implement:
  `MockCredentialExchanger.exchange` still returned a bare `AuthCredential`
  rather than `ExchangeResult`; `DummyAgent` and `MockLlm` were missing
  abstract members added since.
- `Content['parts']![0]`: `!` is not valid inside a type, so eight sites were
  silently typed `Part[] | undefined`. The literals type-check as `Part` with
  no cast at all now.
- Two stale `@ts-expect-error` directives for errors that no longer occur.

Also fixes one genuine source-side typing bug this surfaced: `dev`'s
integration registry declared its tools as bare `FunctionTool`, which means
`FunctionTool<undefined>` -- a tool that takes no arguments -- so it could not
actually hold any of the schema-carrying tools it is handed.

Two type/impl inconsistencies are left as documented casts rather than
silently papered over: `ListSessionsRequest.userId` is declared required but
`VertexAiSessionService.listSessions` supports its absence (and a test covers
that path), and `@google-cloud/vertexai` bundles its own `@google/genai`, so
the two `ApiClient` classes are nominally distinct.

* ci: address review on the repo-wide type check

Fix the types the tests were working around, rather than the tests:

- `ListSessionsRequest.userId` was declared required while all three
  implementations branch on its absence, so the test that covers the
  no-filter path needed a cast. It is optional now; the cast and its
  comment are gone and those call sites are back to their original form.
  `InMemorySessionService` was the one site indexing on it, and now lists
  across users when it is omitted, matching the other two.
- `InMemoryPolicyEngine.evaluate()` declared no parameter, so the
  exported class could not be called through `BasePolicyEngine`.

Give the root config an `exclude`: the one it inherits from gts resolves
relative to gts, so the check was also compiling the 270 `dist/**/*.d.ts`
files the preceding build step emits.

Pin the workflow's actions to commit SHAs, matching #634, so zizmor's
unpinned-uses check passes on the file this PR touches.
The Python snippets on https://adk.dev/graphs/ have no TypeScript counterpart,
and they are fragments: they reference helpers they never define (`condition()`,
`task_A_node`, …), so they cannot be run as written even in Python. A TS reader
has nothing to copy from and no way to check that the concept behaves the way
the page claims.

Adds 26 runnable ports, one directory per snippet, grouped by the docs page it
comes from so a directory maps 1:1 to a section anchor on adk.dev:

  graphs/         get_started, process_pipeline
  routes/         sequence, branches, function_node, fan_out_join,
                  loop_escalation, nested_workflow
  data_handling/  node_output, routing_output, schemas, session_state,
                  structured_access, structured_output, user_message
  dynamic/        get_started, nodes, custom_run_ids, data_handling,
                  human_input, loop_route, parallel_route, sequence_route
  human_input/    get_started, initial_prompt, payload_and_schema

Each fills in the undefined helpers with the smallest plausible implementation
and says so in its header. Where TypeScript genuinely diverges from the Python
API the file comments say why, so a reader porting from the docs is not left
guessing — for example Python's `Event(message=...)` has no TS equivalent, and
a graph's validating schema belongs on the node wrapping an agent rather than
on the agent itself.

18 of the 26 run with no API key, which keeps the concepts (routing, loops,
fan-out/join, dynamic dispatch, human-in-the-loop) explorable offline.
samples/ is not an npm workspace, so "npm run build" never compiled it, and
the lint job uses tseslint's non-type-aware recommended config. That left the
sample sources backing the docs pages with nothing in CI that would catch a
renamed type or a removed export as the @experimental workflow API moves.

Add samples/tsconfig.json (the same extends-the-root pattern core, dev and
integrations use), a "ts:check:samples" script, and a validation.yaml step
that runs it after the build. Scoped to samples rather than the existing
repo-wide "ts:check", which currently reports 288 pre-existing errors across
44 test files.
Both were review findings, and both were wrong about the framework rather
than about the samples.

The dynamic HITL sample said the `rerun_on_resume=False` handoff -- "do not
re-run on resume; complete with the human's reply as my output" -- was
implemented for static graph nodes only, so its leaf used a re-entry form
instead: a stable `interruptId` plus a `ctx.resumeInputs[id]` lookup that
returns the reply on the second pass. #635 added that handoff for dynamic
`ctx.runNode` children (`dynamic_node_scheduler.ts:134`, `resumeHandoff`), so
the claim went stale in the same branch that now carries the sample. The leaf
is the doc's `rerun_on_resume=False` one-liner again, which is both the
faithful port and four fewer concepts to explain.

The node_output sample cautioned that a node may emit only ONE event carrying
`output`. Nothing enforces that: `node_runner.ts:234` assigns
`child.output = event.output` for every event, so the last one silently wins
and the successor never sees the rest. That is worth stating precisely,
because the Python page gives two accounts and neither is what happens here --
each `yield` "adds to a list of data objects on the Event" under Node output,
and two yields carrying `Event.output` are "a runtime error" under the
structured-data caution. Recorded as a Python-to-TypeScript difference in the
README rather than only in the sample.

Verified both by running them, not by reading: a node yielding two `output`
events hands the successor the second and raises nothing, and the reworked
HITL leaf pauses on turn 1 and resolves "yes" to "Approved" on turn 2.
Review finding: the samples were split on how they treat the workflow input.
Eleven files wrapped it in `String(...)`; eight called `.trim()` or
`.toUpperCase()` straight on a parameter already declared `string`.

`extractWorkflowInput` (`workflow_agent.ts:187`) returns the message text for a
text-only turn and the raw `Content` for anything else, so neither form is
sound for a non-text turn -- but they fail differently. `String()` turns a
`Content` into `"[object Object]"` and carries it happily through the graph;
the bare call throws where the mistake is. Keep the one that fails loudly, and
say so in the README so a reader copying a sample knows what it assumes.

Coercion stays where the value genuinely is untyped: `ctx.runNode(...).output`
and a `ctx.resumeInputs[id]` reply are both `unknown`, and the samples that
read them keep converting explicitly at the point of use.
Review finding: eslint, Prettier, check_license.sh and the new `tsc` step all
read `samples/`, so a syntax, style, license or type error in these 26 files
fails CI. Nothing ran them, which left the failure they are most exposed to
uncovered: a `WorkflowAgent` validates its graph in its constructor, so a
rename or a semantics change in the `@experimental` workflow API can turn a
sample into a load-time error that still type-checks -- and #635, #637 and
#647 all moved that API while this branch was open.

Every sample is now constructed, and the 18 that call no model are also run
end-to-end through a real `InMemoryRunner`. Reuses the existing sample harness
in `offline` mode, which installs the record/replay model over an empty
response set, so an "offline" sample that starts calling a model throws rather
than reaching the network. The 8 model-backed samples are constructed only:
driving them means a checked-in fixture each, and what they add over the
sibling `tests/integration/workflows/` set is prompt wording, not graph shape.

One table drives it, and a guard test asserts the table matches the
directories on disk -- otherwise a new sample silently gets no coverage, which
is the hole this closes.

Checked against all three failures it is meant to catch, rather than assuming
a passing suite means a working one: a duplicate node name (constructor
validation) fails the sample's case, an unregistered new directory fails the
guard, and an LlmAgent spliced into an offline graph fails on the missing
fixture.
…he read

The third stale claim of this review, and the same shape as the other two: a
sample working around a framework bug that has since been fixed on main.

The session_state port carried a "do not read-modify-write ONE key from several
nodes" gotcha, and routed `attempts` along the edges as node output to avoid
it. #636 landed that fix — node reads are now served from a per-invocation
write overlay — so the warning describes a bug that no longer exists and the
workaround is no longer buying anything.

`attempts` goes back to being a state key that one node initializes, another
increments and a third reads, which is what the Python snippet does and what
its inline comment claims it prints. Confirmed against both sides of the fix
rather than assuming: reverting #636's `node_context.ts` makes the third node
read 0, and with it in place the sample prints `attempts state: 1` — the
snippet's own documented output.

Drops the README gotcha section with it, and keeps the surviving half of the
advice — prefer an edge when only the next node needs the value — as guidance
in the sample rather than as a warning about a defect.
The samples config inherits the root one, so once #648 adds the
`@google/adk` -> `core/src` aliases there, `npm run ts:check:samples`
would start checking the samples against the workspace sources instead of
the published types — the one thing a sample should not do, since a user's
project resolves the package through `node_modules`.

`"paths": {}` pins that, the same reset `core`, `dev` and `integrations`
already carry. No-op against the root config as it stands today: the check
resolves to `core/dist/types/index.d.ts` and passes either way.

@AmaadMartin AmaadMartin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Both blocking findings are closed in code at 0a0238e; I checked the fixes, not the replies. The human_input deletion is sound: resumeHandoff (dynamic_node_scheduler.ts:134) applies the same four guards as the static path in workflow.ts:391, no reference to the removed identifiers survives anywhere in the repo, and the new integration test drives the pause and the resume. The node_output text now matches node_runner.ts:235, and you are right that the Python page contradicts itself — thank you for finding the second passage. Both nits are closed too, and restoring the state counter on #636 is correct. I cannot approve yet: three run-tests jobs are pending.

Comment thread samples/tsconfig.json
Comment on lines +4 to +9
// The root config aliases `@google/adk` to `core/src`, so that a test
// type-checks against the sources vitest runs it against. A sample is a
// consumer, not part of the build, so it has to resolve the package the
// way a user's project does: through `node_modules`, against the
// published types. `core`, `dev` and `integrations` reset this for the
// same reason.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit. This comment describes #648's tree, not this one.

At 0a0238e the root tsconfig.json declares no paths, and core, dev and integrations carry no reset either. All of that arrives with #648.

So "paths": {} is correct and harmless, and it is the right thing to have here — but it is inert today, and the reason given for it is not true yet. Either land this after #648, or write the comment in the future tense.

Fallout from rebasing onto #648, which landed the repo-wide `ts:check` while
this branch was open. The root config names no `include` and excludes only
`node_modules` and `**/dist`, so `tsc --noEmit` now picks up all 26 sample
files — and resolves their `@google/adk` imports through the root `paths`
aliases, against `core/src`.

That is the one resolution a sample must not use, which is the whole point of
the `"paths": {}` reset in `samples/tsconfig.json`: a sample is a consumer of
the published package, so it has to resolve the way a user's project does,
through `node_modules` and against the built types. With both checks running,
the scoped one did that and the repo-wide one quietly did the opposite over the
same files.

Excluding `samples` from the root config leaves one owner. Verified on both
sides: `tsc --noEmit --listFiles` now reports 0 files under `samples/` and
still passes, while `tsc -p samples --listFiles` reports all 26 and resolves
`@google/adk` to `core/dist/types/index.d.ts`.

The `validation.yaml` collision #648 was warned about resolved as both steps,
not one: `ts:check` for the repo, `ts:check:samples` for the samples. The
zizmor hardening on that file (`permissions`, `persist-credentials`, the three
SHA pins) came in with #648, so that commit dropped out of this branch as
already upstream.
@kalenkevich
kalenkevich force-pushed the docs/workflow-samples branch from 0a0238e to 09c3146 Compare August 12, 2026 02:18
Comment thread samples/tsconfig.json
Comment on lines +4 to +9
// The root config aliases `@google/adk` to `core/src`, so that a test
// type-checks against the sources vitest runs it against. A sample is a
// consumer, not part of the build, so it has to resolve the package the
// way a user's project does: through `node_modules`, against the
// published types. `core`, `dev` and `integrations` reset this for the
// same reason.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nit, withdrawn. Keep "paths": {} and its comment as they are.

#648 merged at 02:13Z, four minutes before I posted. The head I read, 0a0238e, sits on fcc6c1e7 (#636) and predates it, so the root paths and the sibling resets really were absent — but only there. Current main carries both, so your comment is accurate and the reset goes live on the rebase. I should have checked whether #648 had landed before I filed this.

Your only conflict is .github/workflows/validation.yaml:44-45, where main now has #648's Run type check step. The hardening lines merge clean; both sides are identical.

@AmaadMartin AmaadMartin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved at 09c3146e. All 12 checks pass and no findings remain open.

Both blocking findings are closed in code, not only claimed. git grep APPROVAL_INTERRUPT_ID returns nothing repo-wide, resumeHandoff applies the same guards as the static path (workflow.ts:391), and the new integration test drives dynamic/human_input through pause-then-resume, so the deleted resume logic has executing coverage.

The rebase onto #648 left every file I verified byte-identical, and "paths": {} is now live and correct. My nit about that comment is withdrawn: it was accurate only against a base that #648 had already replaced.

run-tests (windows-latest) failed once on build_setup_test.ts at a 20s timeout, then passed on the re-run. main fails the same job on a different test, so the runner is unstable and the failure was not from this diff. The new sample test passed on Windows both times.

@ScottMansfield
ScottMansfield merged commit 2515bb1 into main Aug 12, 2026
14 of 15 checks passed
@ScottMansfield
ScottMansfield deleted the docs/workflow-samples branch August 12, 2026 05:37
prasanna8585 pushed a commit to prasanna8585/adk-js that referenced this pull request Aug 21, 2026
* ci: type-check the repo in CI

`npm run ts:check` (`tsc --noEmit`) existed but was wired into nothing.
validation.yaml runs secretlint, build, tests, lint, format and docs; the
build only compiles the three workspaces' `src/`, and `eslint.config.js` uses
tseslint's non-type-aware recommended config. So no job compiled a single test
file, and `tsc --noEmit` had drifted to 287 errors across 44 files.

Two thirds of those were one bug, not 200. A test that imports both
`@google/adk` and `../../src/...` got two distinct identities for the same
class, because vitest aliases `@google/adk` to `core/src` while tsc resolved it
to the built `core/dist/types`. Mirroring the vitest aliases as `paths` in the
root tsconfig drops it to 164. The per-workspace configs reset `paths` to `{}`
so a published build still resolves through node_modules -- verified by
diffing the emitted declarations before and after: byte-identical.

The remaining errors were real drift in test files, fixed rather than
suppressed -- no `@ts-ignore`, no `as any`, and 22 casts removed against 15
added. The recurring ones:

- `LiveServerMessage` is a class with `text`/`data` getters, so the ~45 object
  literals pushed at it were never assignable. Added one shared
  `liveServerMessage()` helper that builds a real instance.
- String literals where a genai enum member is required (`Modality`,
  `BlockedReason`, `Type`, `Language`, `FileContentEncoding`). The enum values
  are the same strings, so the assertions are untouched.
- Mocks whose signatures had drifted from the type they implement:
  `MockCredentialExchanger.exchange` still returned a bare `AuthCredential`
  rather than `ExchangeResult`; `DummyAgent` and `MockLlm` were missing
  abstract members added since.
- `Content['parts']![0]`: `!` is not valid inside a type, so eight sites were
  silently typed `Part[] | undefined`. The literals type-check as `Part` with
  no cast at all now.
- Two stale `@ts-expect-error` directives for errors that no longer occur.

Also fixes one genuine source-side typing bug this surfaced: `dev`'s
integration registry declared its tools as bare `FunctionTool`, which means
`FunctionTool<undefined>` -- a tool that takes no arguments -- so it could not
actually hold any of the schema-carrying tools it is handed.

Two type/impl inconsistencies are left as documented casts rather than
silently papered over: `ListSessionsRequest.userId` is declared required but
`VertexAiSessionService.listSessions` supports its absence (and a test covers
that path), and `@google-cloud/vertexai` bundles its own `@google/genai`, so
the two `ApiClient` classes are nominally distinct.

* ci: address review on the repo-wide type check

Fix the types the tests were working around, rather than the tests:

- `ListSessionsRequest.userId` was declared required while all three
  implementations branch on its absence, so the test that covers the
  no-filter path needed a cast. It is optional now; the cast and its
  comment are gone and those call sites are back to their original form.
  `InMemorySessionService` was the one site indexing on it, and now lists
  across users when it is omitted, matching the other two.
- `InMemoryPolicyEngine.evaluate()` declared no parameter, so the
  exported class could not be called through `BasePolicyEngine`.

Give the root config an `exclude`: the one it inherits from gts resolves
relative to gts, so the check was also compiling the 270 `dist/**/*.d.ts`
files the preceding build step emits.

Pin the workflow's actions to commit SHAs, matching google#634, so zizmor's
unpinned-uses check passes on the file this PR touches.
prasanna8585 pushed a commit to prasanna8585/adk-js that referenced this pull request Aug 21, 2026
…oc snippets (google#634)

* docs(workflow): add runnable ports of the graph-workflow doc snippets

The Python snippets on https://adk.dev/graphs/ have no TypeScript counterpart,
and they are fragments: they reference helpers they never define (`condition()`,
`task_A_node`, …), so they cannot be run as written even in Python. A TS reader
has nothing to copy from and no way to check that the concept behaves the way
the page claims.

Adds 26 runnable ports, one directory per snippet, grouped by the docs page it
comes from so a directory maps 1:1 to a section anchor on adk.dev:

  graphs/         get_started, process_pipeline
  routes/         sequence, branches, function_node, fan_out_join,
                  loop_escalation, nested_workflow
  data_handling/  node_output, routing_output, schemas, session_state,
                  structured_access, structured_output, user_message
  dynamic/        get_started, nodes, custom_run_ids, data_handling,
                  human_input, loop_route, parallel_route, sequence_route
  human_input/    get_started, initial_prompt, payload_and_schema

Each fills in the undefined helpers with the smallest plausible implementation
and says so in its header. Where TypeScript genuinely diverges from the Python
API the file comments say why, so a reader porting from the docs is not left
guessing — for example Python's `Event(message=...)` has no TS equivalent, and
a graph's validating schema belongs on the node wrapping an agent rather than
on the agent itself.

18 of the 26 run with no API key, which keeps the concepts (routing, loops,
fan-out/join, dynamic dispatch, human-in-the-loop) explorable offline.

* ci(samples): type-check samples/ in CI

samples/ is not an npm workspace, so "npm run build" never compiled it, and
the lint job uses tseslint's non-type-aware recommended config. That left the
sample sources backing the docs pages with nothing in CI that would catch a
renamed type or a removed export as the @experimental workflow API moves.

Add samples/tsconfig.json (the same extends-the-root pattern core, dev and
integrations use), a "ts:check:samples" script, and a validation.yaml step
that runs it after the build. Scoped to samples rather than the existing
repo-wide "ts:check", which currently reports 288 pre-existing errors across
44 test files.

* docs(workflow): correct two wrong claims in the sample comments

Both were review findings, and both were wrong about the framework rather
than about the samples.

The dynamic HITL sample said the `rerun_on_resume=False` handoff -- "do not
re-run on resume; complete with the human's reply as my output" -- was
implemented for static graph nodes only, so its leaf used a re-entry form
instead: a stable `interruptId` plus a `ctx.resumeInputs[id]` lookup that
returns the reply on the second pass. google#635 added that handoff for dynamic
`ctx.runNode` children (`dynamic_node_scheduler.ts:134`, `resumeHandoff`), so
the claim went stale in the same branch that now carries the sample. The leaf
is the doc's `rerun_on_resume=False` one-liner again, which is both the
faithful port and four fewer concepts to explain.

The node_output sample cautioned that a node may emit only ONE event carrying
`output`. Nothing enforces that: `node_runner.ts:234` assigns
`child.output = event.output` for every event, so the last one silently wins
and the successor never sees the rest. That is worth stating precisely,
because the Python page gives two accounts and neither is what happens here --
each `yield` "adds to a list of data objects on the Event" under Node output,
and two yields carrying `Event.output` are "a runtime error" under the
structured-data caution. Recorded as a Python-to-TypeScript difference in the
README rather than only in the sample.

Verified both by running them, not by reading: a node yielding two `output`
events hands the successor the second and raises nothing, and the reworked
HITL leaf pauses on turn 1 and resolves "yes" to "Approved" on turn 2.

* docs(workflow): stop coercing inputs that are already typed as strings

Review finding: the samples were split on how they treat the workflow input.
Eleven files wrapped it in `String(...)`; eight called `.trim()` or
`.toUpperCase()` straight on a parameter already declared `string`.

`extractWorkflowInput` (`workflow_agent.ts:187`) returns the message text for a
text-only turn and the raw `Content` for anything else, so neither form is
sound for a non-text turn -- but they fail differently. `String()` turns a
`Content` into `"[object Object]"` and carries it happily through the graph;
the bare call throws where the mistake is. Keep the one that fails loudly, and
say so in the README so a reader copying a sample knows what it assumes.

Coercion stays where the value genuinely is untyped: `ctx.runNode(...).output`
and a `ctx.resumeInputs[id]` reply are both `unknown`, and the samples that
read them keep converting explicitly at the point of use.

* test(workflow): execute the docs samples instead of only compiling them

Review finding: eslint, Prettier, check_license.sh and the new `tsc` step all
read `samples/`, so a syntax, style, license or type error in these 26 files
fails CI. Nothing ran them, which left the failure they are most exposed to
uncovered: a `WorkflowAgent` validates its graph in its constructor, so a
rename or a semantics change in the `@experimental` workflow API can turn a
sample into a load-time error that still type-checks -- and google#635, google#637 and
google#647 all moved that API while this branch was open.

Every sample is now constructed, and the 18 that call no model are also run
end-to-end through a real `InMemoryRunner`. Reuses the existing sample harness
in `offline` mode, which installs the record/replay model over an empty
response set, so an "offline" sample that starts calling a model throws rather
than reaching the network. The 8 model-backed samples are constructed only:
driving them means a checked-in fixture each, and what they add over the
sibling `tests/integration/workflows/` set is prompt wording, not graph shape.

One table drives it, and a guard test asserts the table matches the
directories on disk -- otherwise a new sample silently gets no coverage, which
is the hole this closes.

Checked against all three failures it is meant to catch, rather than assuming
a passing suite means a working one: a duplicate node name (constructor
validation) fails the sample's case, an unregistered new directory fails the
guard, and an LlmAgent spliced into an offline graph fails on the missing
fixture.

* docs(workflow): restore the state-based counter now that google#636 fixed the read

The third stale claim of this review, and the same shape as the other two: a
sample working around a framework bug that has since been fixed on main.

The session_state port carried a "do not read-modify-write ONE key from several
nodes" gotcha, and routed `attempts` along the edges as node output to avoid
it. google#636 landed that fix — node reads are now served from a per-invocation
write overlay — so the warning describes a bug that no longer exists and the
workaround is no longer buying anything.

`attempts` goes back to being a state key that one node initializes, another
increments and a third reads, which is what the Python snippet does and what
its inline comment claims it prints. Confirmed against both sides of the fix
rather than assuming: reverting google#636's `node_context.ts` makes the third node
read 0, and with it in place the sample prints `attempts state: 1` — the
snippet's own documented output.

Drops the README gotcha section with it, and keeps the surviving half of the
advice — prefer an edge when only the next node needs the value — as guidance
in the sample rather than as a warning about a defect.

* ci(samples): keep samples resolving @google/adk through node_modules

The samples config inherits the root one, so once google#648 adds the
`@google/adk` -> `core/src` aliases there, `npm run ts:check:samples`
would start checking the samples against the workspace sources instead of
the published types — the one thing a sample should not do, since a user's
project resolves the package through `node_modules`.

`"paths": {}` pins that, the same reset `core`, `dev` and `integrations`
already carry. No-op against the root config as it stands today: the check
resolves to `core/dist/types/index.d.ts` and passes either way.

* ci(samples): keep the repo-wide type check out of samples/

Fallout from rebasing onto google#648, which landed the repo-wide `ts:check` while
this branch was open. The root config names no `include` and excludes only
`node_modules` and `**/dist`, so `tsc --noEmit` now picks up all 26 sample
files — and resolves their `@google/adk` imports through the root `paths`
aliases, against `core/src`.

That is the one resolution a sample must not use, which is the whole point of
the `"paths": {}` reset in `samples/tsconfig.json`: a sample is a consumer of
the published package, so it has to resolve the way a user's project does,
through `node_modules` and against the built types. With both checks running,
the scoped one did that and the repo-wide one quietly did the opposite over the
same files.

Excluding `samples` from the root config leaves one owner. Verified on both
sides: `tsc --noEmit --listFiles` now reports 0 files under `samples/` and
still passes, while `tsc -p samples --listFiles` reports all 26 and resolves
`@google/adk` to `core/dist/types/index.d.ts`.

The `validation.yaml` collision google#648 was warned about resolved as both steps,
not one: `ts:check` for the repo, `ts:check:samples` for the samples. The
zizmor hardening on that file (`permissions`, `persist-credentials`, the three
SHA pins) came in with google#648, so that commit dropped out of this branch as
already upstream.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants